C++ boost库中的coroutine2和fiber(入门指南)

boost fiber介绍中讲到:“在x86上,线程通常需要花费数千个CPU周期进行切换,而fiber只需要小于100个。”

定义:coroutine本文称为协程,fiber称为纤程。coroutine需要开发者手动调度;fiber更适合工业界,它是对coroutine进行了封装,增加了调度器。

coroutine2

boost库中coroutine是旧版本(已废弃),而新的coroutine2支持c++11。

coroutine2主要有两种类型:push_type和pull_type,有git经验的小伙伴一定十分面熟。可以想象,他们是一个pair <push, pull>关系,

  • pull 拉取push者的数据,其中pull.get()只拉取数据,不引起协程切换;
  • push 将数据推给pull者;
  • push_type和pull_type都重载了operator()运算符,可以当仿函数用;
  • push_type 会先被调用。

很显然,这个数据流向是单向的,总是从push->pull。而且,不管是调用push还是pull都会引起协程切换。

coroutine2例子,[https://github.com/qingdujun/basic/blob/master/coroutine/coroutine2-example.cpp]

#include <iostream>
#include <boost/coroutine2/all.hpp>

void foo(boost::coroutines2::coroutine<int>::pull_type& pull) {
std::cout << "a";
std::cout << pull.get();
std::cout << "b";
pull();
std::cout << "c";
for (auto val : pull) {
std::cout << val;
}
}

int main(int argc, char const *argv[]) {
boost::coroutines2::coroutine<int>::push_type push(foo);

std::cout << "x";
push(1);
std::cout << "y";
push(2);
std::cout << "z";
push(3);
std::cout << "i";
push(4);
std::cout << "j";

return 0;
}

运行结果:xa1byc2z3i4j

fiber

fiber是一个coroutine2的工业级实现,封装了很多函数,使用起来比coroutine2更加方便。看起来和Go语言的协程goroutine很相似。一些我从官网上了解到的信息,

  • buffered_channel 提供的是一个多生产者多消费者(MPMC)队列,适用于消息异步传输。MPMC队列容量必须为2的倍数。
  • fiber有4种模板的构造函数 包括fiber( Fn && fn, Args && ... args);和fiber( launch policy, Fn && fn, Args && ... args);等。
  • fiber launch 默认的lauch方式是post(就绪,等待调度),也可以设置为dispatch(将会立即被调度)。

以下例子定义buffered_channel容量为4,同时使用了2个fiber,并使用buffered_channel传输参数,功能基本上和上面的coroutine2例子一致。

fiber例子,[https://github.com/qingdujun/basic/blob/master/coroutine/fiber/fiber-example.cpp]

#include <iostream>
#include <string>
#include <boost/fiber/all.hpp>

void foo(boost::fibers::buffered_channel<std::string>& channel) {
channel.push("hello");
channel.push(",");
channel.push("Web");
channel.push("Surfing");
channel.push("!");
channel.close();
}

void bar(boost::fibers::buffered_channel<std::string>& channel) {
for (std::string& s : channel) {
std::cout << s << " ";
}
channel.close();
}

int main(int argc, char const *argv[]) {
boost::fibers::buffered_channel<std::string> channel(4);
boost::fibers::fiber fb1(foo, std::ref(channel));
boost::fibers::fiber fb2(std::bind(bar, std::ref(channel)));

for (int i = 0; i < 50; ++i) {
std::cout <<"aaaaa"<< std::endl;
}

fb1.join();
fb2.join();
std::cout << std::endl;

return 0;
}

运行结果:hello , Web Surfing !

以上看起来,coroutine2和fiber好像没什么区别,那么再举个例子,展现一下fiber调度器的威力。让协程看起来和轻量级线程一样。你会发现,好像就是将thread关键词换成了fiber,使用起来很方便。

  • 线程是由操作系统调度的;
  • 协程是运行在一个或多个线程中,由程序自行调度;
  • yield 会使协程让出当前执行权力,引起调度。

4个纤程例子,[https://github.com/qingdujun/basic/blob/master/coroutine/fiber/simple_fiber.cpp]

#include <iostream>
#include <string>
#include <boost/fiber/all.hpp>

void fn(std::string const& str, int n) {
for ( int i = 0; i < n; ++i) {
std::cout << str << ": " << i << std::endl;
boost::this_fiber::yield();
}
}

int main() {
boost::fibers::fiber f1(fn, "abc", 15);
boost::fibers::fiber f2(fn, "efg", 2);
boost::fibers::fiber f3(fn, "hij", 8);
boost::fibers::fiber f4(fn, "klm", 10);

// std::cerr << "f1 : " << f1.get_id() << std::endl;
f1.join();
f2.join();
f3.join();
f4.join();
std::cout << "done." << std::endl;

return 0;
}

运行结果,

MacBook-Pro:fiber qingdujun$ ./example 
abc: 0
efg: 0
hij: 0
klm: 0
abc: 1
efg: 1
hij: 1
klm: 1
abc: 2
hij: 2
klm: 2
abc: 3
hij: 3
klm: 3
abc: 4
...

done.

附上CMakeLists.txt

直接使用g++编译程序的时候,总是出问题,要么就是coroutine2找不到,要么就是fiber找不到。但是,使用makefile就可以,很奇怪。其他的makefile文件,可以自行在Github中找到。

# 2019-07-12 by Web Surfing(qingdujun(at)outlook.com)
#
cmake_minimum_required (VERSION 2.8)
project (fiber-example)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

find_package(Boost REQUIRED COMPONENTS context fiber)

if(Boost_FOUND)
add_executable (example fiber-example.cpp)
target_link_libraries(example Boost::fiber Boost::context)
else()
message(err: Boost fiber not found)
endif()

References:

[1] 开学五年级了, Boost - 从Coroutine2 到Fiber [2] boost fiber, https://github.com/boostorg/fiber [3] guxch, Boost中的协程—Boost.Coroutine2 [4] stackoverflow, cannot compile boost::fiber official examples [5] boost, Asymmetric coroutine [6] boost fiber, https://www.boost.org/doc/libs/1_70_0/libs/fiber/doc/html/index.html [7] boost fiber, Buffered Channel [8] boost, fiber launch