boost 1_66 无栈协程 coroutine 学习
安装boost库
yum直接安装:
1、yum install boost
2、yum install boost-devel
3、yum install boosat-doc
PS:注意yum安装的版本,有些旧boost版本还没有协程库
手动编译安装 boost_1_66_0.tar.gz
1、tar -vxzf boost_1_66_0.tar.gz
2、cd boost_1_66_0
3、./bootstrap.sh
4、./b2 install --prefix=/usr
5、ldconfig -v
检验是否安装成功
#include <iostream>
#include <boost/version.hpp>
#include <boost/config.hpp>
using namespace std;
int main()
{
cout << "BOOST_VERSION: " << BOOST_VERSION << endl;
cout << "BOOST_LIB_VERSION: " << BOOST_LIB_VERSION << endl;
cout << "BOOST_PLATFORM: " << BOOST_PLATFORM << endl;
cout << "BOOST_COMPILER: " << BOOST_COMPILER << endl;
cout << "BOOST_STDLIB: " << BOOST_STDLIB << endl;
return 0;
}
执行结果
BOOST_VERSION: 106600
BOOST_LIB_VERSION: 1_66
BOOST_PLATFORM: linux
BOOST_COMPILER: GNU C++ version 4.8.5 20150623 (Red Hat 4.8.5-44)
BOOST_STDLIB: GNU libstdc++ version 20150623
安装成功
boost官网关于无栈协程介绍
主要来源于boost官网关于stackless协程的介绍
类成员 | 含义 |
---|---|
coroutine | 构造成为初始化状态 |
is_child | 如果是一个fork子协程的话返回true |
is_complete | 如果到了终止状态就返回true |
is_parent | 如果是fork的父协程的话返回true |
coroutine 类可以用来实现无栈的协程。这个类自身被用来保存协程的状态。
coroutine 类可以支持复制构造和赋值。最大一个int的空间占用。可以当作基类使用
reenter
用来定义一段协程,只接受一个coroutine的指针或引用,鉴于coroutine本身可以当作基类,你也可以用类似reenter(this) {…}的写法,当reenter被执行的时候,控制流会跳转到最后yield或者fork的位置。需要注意的是reenter宏是通过switch实现的,并不是类似线程那种上下文的保存和切换达到的,意味着当在协程体中使用局部变量的时候,重入协程体时候不能忽略局部变量的定义。
yield {statement}
常常用在异步操作的时候,其执行的逻辑为:yield保存了当前协程的状态;其表达式初始化了异步操作;定义恢复点为statement后的一条语句;控制流被转移到了协程体的结尾。当异步操作结束的时候,函数对象重新被唤醒,然后reenter使得执行流转移到了恢复点。当然statement表达式也可以是复合表达式,用花括号框起,见boost_demo2
yield return {expression}
通常用于生成器的环境下使用,其return后面的值作为函数的返回值传递出来,同时不会继续执行reenter后的代码
yield break
主要用来终止协程的,yield首先设置协程的终止状体,然后流程被转移到了协程体的结尾。一旦终止,使用is_complete()就会返回true,同时协程不能够被再次reenter了。当然不一定要yield break,当流程执行到了协程体结尾,这些协程也会自动terminate了。这也很好理解,因为stackless协程的reenter本来就是用switch实现的。
fork {statement}
可以创建多个协程的拷贝,常用的情况是在服务端,协程被fork出来用于处理客户端的请求。父协程和子协程通过is_parent()、is_child()进行界定。
无栈协程源码
boost库中无栈协程的源码非常简洁明了且和boost其他的模块没有任何耦合,完全可以将该部分提出来单独使用,其源码只在boost/asio/coroutine.hpp和boost/asio/yield.hpp中,这里默认在Linux环境下运行,去掉一些编译相关的选项,源码只有如下这部分
include <iostream>
//coroutine_basic.h
class coroutine_ref;
class coroutine
{
public:
/// Constructs a coroutine in its initial state.
coroutine() : value_(0) {}
/// Returns true if the coroutine is the child of a fork.
bool is_child() const { return value_ < 0; }
/// Returns true if the coroutine is the parent of a fork.
bool is_parent() const { return !is_child(); }
/// Returns true if the coroutine has reached its terminal state.
bool is_complete() const { return value_ == -1; }
private:
friend class coroutine_ref;
int value_;
};
class coroutine_ref
{
public:
coroutine_ref(coroutine& c) : value_(c.value_), modified_(false) {}
coroutine_ref(coroutine* c) : value_(c->value_), modified_(false) {}
~coroutine_ref() { if (!modified_) value_ = -1; }
operator int() const { return value_; }
int& operator=(int v) { modified_ = true; return value_ = v; }
private:
void operator=(const coroutine_ref&);
int& value_;
bool modified_;
};
#define BOOST_ASIO_CORO_REENTER(c) \
switch (coroutine_ref _coro_value = c) \
case -1: if (_coro_value) \
{ \
goto terminate_coroutine; \
terminate_coroutine: \
_coro_value = -1; \
goto bail_out_of_coroutine; \
bail_out_of_coroutine: \
break; \
} \
else /* fall-through */ case 0:
#define BOOST_ASIO_CORO_YIELD_IMPL(n) \
for (_coro_value = (n);;) \
if (_coro_value == 0) \
{ \
case (n): ; \
break; \
} \
else \
switch (_coro_value ? 0 : 1) \
for (;;) \
/* fall-through */ case -1: if (_coro_value) \
goto terminate_coroutine; \
else for (;;) \
/* fall-through */ case 1: if (_coro_value) \
goto bail_out_of_coroutine; \
else /* fall-through */ case 0: \
#define BOOST_ASIO_CORO_FORK_IMPL(n) \
for (_coro_value = -(n);; _coro_value = (n)) \
if (_coro_value == (n)) \
{ \
case -(n): ; \
break; \
} \
else
# define BOOST_ASIO_CORO_YIELD BOOST_ASIO_CORO_YIELD_IMPL(__LINE__)
# define BOOST_ASIO_CORO_FORK BOOST_ASIO_CORO_FORK_IMPL(__LINE__)
# define reenter(c) BOOST_ASIO_CORO_REENTER(c)
# define yield BOOST_ASIO_CORO_YIELD
# define fork BOOST_ASIO_CORO_FORK
在了解无栈协程的源码前,可以先了解一种C语言的编程技巧----达夫设备 Duff’s Device,其本意是通过程序员手动展开代码以减少循环次数,利用switch-case中case标签的fall-through特性,随着编译器的发展,现在很多编译器已经能自动去做这样一件事情了,可这个编程思路还是有助于我们实现一种朴素的协程,也就是上面代码中无栈协程的实现
#include "coroutine_basic.h" //上文中从boost库提取的无栈协程源码
#include <iostream>
using namespace std;
coroutine c;
void foo(int i)
{
cout << "before reenter " << i << endl;
reenter(c)
{
yield cout << "foo1 " << i+1 << endl;
yield cout << "foo2 " << i+1 << endl;
}
cout << "after reenter" << i << endl;
}
int main()
{
foo(1);
foo(2);
foo(3);
return 0;
}
运行结果
before reenter 1
foo1 2
after reenter1
before reenter 2
foo2 3
after reenter2
before reenter 3
after reenter3
看下该段示例代码的预编译后的foo函数代码片段
void foo(int i)
{
cout << "before reenter " << i << endl;
switch (coroutine_ref _coro_value = c) case -1: if (_coro_value) { goto terminate_coroutine; terminate_coroutine: _coro_value = -1; goto bail_out_of_coroutine; bail_out_of_coroutine: break; } else case 0:
{
for (_coro_value = (12);;) if (_coro_value == 0) { case (12): ; break; } else switch (_coro_value ? 0 : 1) for (;;) case -1: if (_coro_value) goto terminate_coroutine; else for (;;) case 1: if (_coro_value) goto bail_out_of_coroutine; else case 0: cout << "foo1 " << i+1 << endl;
for (_coro_value = (13);;) if (_coro_value == 0) { case (13): ; break; } else switch (_coro_value ? 0 : 1) for (;;) case -1: if (_coro_value) goto terminate_coroutine; else for (;;) case 1: if (_coro_value) goto bail_out_of_coroutine; else case 0: cout << "foo2 " << i+1 << endl;
}
cout << "after reenter" << i << endl;
}
可以看到,可重入是通过传入函数的引用coroutine_ref _coro_value改变了全局变量coroutine c的value值,第二次进入foo函数后直接通过swith(12) break执行第13行。