C++中的yield和fork

来源:互联网 发布:手机淘宝怎么改号码 编辑:程序博客网 时间:2024/06/05 06:28

各位看官,您没有看错,C++是可以有yield和fork的,这个主题小麦很早以前就打算写,只是一直没有一个契机给我这个动力。前段日子,小麦帮朋友处理一个用单线程模拟多线程的活儿的时候,再次想到了这个事情,决定写一下,也算是自己的一个回顾。本文其实也可以叫做boost::asio::coroutine原理解析。。。

当然,C++本身是没有yield这个关键字的,这个关键字的意义可以参考C#对其的定义,就是跳出当前的执行过程,下次调用这个过程的时候,直接从yield处开始执行。例如

int foo(){    yield return 1;    yield return 2;    yield return 3;}

连续调用foo函数3次,分别得到了1,2,3这三个数,这就是yield的基本语义,而fork的意思是保存当前上下文,跳转到目标函数,在目标函数执行完成之后,回到当前位置,例如

std::cout<<"line 1"<<std::endl;fork foo()

从上面的例子可以看出,yield和fork的作用就相当于用单线程模拟了多线程的语义。这个语义正是常说的coroutine,即协程。通常来说,协程的实现是保存当前上下文,然后在线程内不断切换上下文,这种情况也叫做stackfull coroutine.本文要描述的是不保存上下文的情况,即stackless coroutine。

怎么用

如果各位看过C#中关于 yield的实现的话,可能会有一些印象,C#把yield语句翻译成了一个状态机,利用状态机记录每次进入函数时应该从何处开始执行。然而在C++中,失去了编译器的支持,想实现这个语义还是颇为困难的。下面的实现是boost asio的作者给出的实现,其逆天程度之高,以致于这个实现最开始并没有出现在boost asio的官方文档中,而是在boost asio的example中静静的躺着,直到最近的boost 1.54,这个技术才正式成为boost asio的一部分,

简而言之,这个实现是用宏实现的,请各位忘记C++的程序跳转,记住C#的yield,我们来看个例子

#include <boost/asio/yield.hpp>#include <boost/asio/coroutine.hpp>#include <iostream>boost::asio::coroutine c;void foo(int i){    reenter(c)    {        yield std::cout<<"foo1 "<<i<<std::endl;        fork foo(100);        yield std::cout<<"foo2 "<< i+1<<std::endl;    }}int main(){    foo(1);    foo(2);    foo(3);    return 0;}

这个程序的输出是。。。

foo1 1foo2 101foo2 3

这个咋看之下有点难以理解,我们先忽略具体的细节,解释一下这是怎么回事。

首先,我们需要声明一个corountine,然后将需要重复进入的代码用reenter包括起来。第一次调用foo的时候,代码执行到第一个yield,此时,foo直接返回。

第二次调用的时候,程序直接执行上一次yield之后的代码,即fork,此时,程序调用foo,需要注意的是,被调用的fork不再执行第一个yield,而是直接从当前语句开始执行,于是得到输出foo2101,返回之后,程序调用fork之后的yield,得到输出foo23

第三次调用的时候,由于不再有任何未执行的yield,因此不再产生任何输出。

很自然的,我们会想像C#中的yield一样使用,类似这样

int nums(){    reenter(c)    {        for(int i = 0; i < 10; i++)        {            yield return i;        }    }}

很遗憾,这是不行的,这会有一个编译错误,Switchcase isin protected scope,这显然让人觉得不可思议,联想到我们写的程序本身就不太像正常的C++代码,我们有必要详细了解一下到底发生了什么。

内部实现

首先,我们先从能看见的类开始,coroutine

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 detail::coroutine_ref;    int value_;};namespace detail {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_;};} // namespace detail

上面这两个类,都很简单,从类名就可以看出来这是啥意思,略过不提。重要的是如何用宏实现reenter、yield、fork。

reenter的定义是一个宏,名字有重定义,如下

#define BOOST_ASIO_CORO_REENTER(c) \    switch (::boost::asio::detail::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:

回想一下,reenter的用法是

reenter(coroutine){ your_code...;}

因此your_code实际被放在case0:中,回想一下_coro_value最初的初始化值是0,因此,第一次确实执行了your_code

yield的定义同样是个宏,定义如下

#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 (;;) \                    case -1: if (_coro_value) \                        goto terminate_coroutine; \                        else for (;;) \                            case 1: if (_coro_value) \                            goto bail_out_of_coroutine; \                                else case 0:

其中n的定义为__COUNTER___。

看到这个宏,估计看官和我最初反应差不多,深切的怀疑这个代码的合法性,因为有这样一种混乱的用法

switch(n){    for(...)        case 1: if(...) }

这个事实上就是将case 1看作普通的类似goto使用的代码标签,也就是说可以通过switch到达,也可以通过for到达!

这里用户的代码仍然在case0中出现,当执行这段代码是,_coro_value的值首先被设置成一个程序标识用的整数n, 在最内层的swithc-case中,_coro_value的值为n,因此,程序跳转到case0,首先执行用户的代码,用户代码执行完后,返回执行for循环,此时执行对应的if语句,即goto bail_out_of_coroutine,这个定义在之前的reenter的宏中,此时将直接break,返回整个reenter的最外层。

再次调用函数时,由于_coro_value的值仍然为n,则直接调用case0,直接执行后续的语句。

fork的实现如下,这个小麦就不再具体解释,各位看官请自行理解,比较简单。

#define BOOST_ASIO_CORO_FORK_IMPL(n) \    for (_coro_value = -(n);; _coro_value = (n)) \        if (_coro_value == (n)) \        { \            case -(n): ; \            break; \        } \        else

至此,我们就理解了前面的程序为什么会出现一个编译错误,因为大量的使用了switch-case,因此程序的层次很重要,不能在reenter中随意修改 yield和fork的层次,否则会导致switch-case不匹配,从而出现编译错误!!

总结

上述的用法并不是唯一的用法,各位还可以继承boost::asio::coroutine,而不是定义变量,更多用法可以参考boost::asio的example。当然也可以和小麦探讨哦~

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 九游充过钱的游戏忘了游戏名怎么办 百度网盘密码忘了申诉不了怎么办 手机号被别人注册了百度账号怎么办 快手被盗找回时出来重置密码怎么办 魅族账号密码和密保都忘记了怎么办 vivo账号的密保问题忘了怎么办 oppo账号密保问题忘了怎么办 小米手机刷了机忘了账号密码怎么办 忘了小米账号的密码是多少怎么办 千牛账号在手机上被限制登录怎么办 违规的千牛账号被限制登录了怎么办 苹果id和锁屏密码忘记了怎么办 感应门的编程密码忘记了怎么办 交易猫买的号被找回了怎么办 uc上我的小说看不了怎么办 微信零钱忘记密码没有银行卡怎么办 九游平台冻结提不了现怎么办 计算机考试报名登录名忘记了怎么办 云顶扑克提现怎么提不出来怎么办 微信正在下载一直0kb怎么办 守望先锋运行时出现意外错误怎么办 信用卡暂停使用怎么办还能恢复吗 新刷乳胶漆墙面一碰一个坑怎么办 夏天开空调冻着了头疼打喷嚏怎么办 桑蚕丝衣服被沐浴露退了色怎么办 空间被别人知道了密码登录了怎么办 三星手机显示解析包出现问题怎么办 三星手机下载解析包出现问题怎么办 两万的流动大棚给整坏了怎么办 劲舞团抽奖领了一样的衣服怎么办 win系统ps界面字体太小怎么办 任何网页都变成监控登录界面怎么办 微信启动录音的尝试被拒绝怎么办 微信传到电脑的文件打不开怎么办 转转网账号出租时遇到防沉迷怎么办 如果买了王者号结果有防沉迷怎么办 苹果手机一直显示验证失败怎么办呢 安装包证书异常导致安装失败怎么办 qq飞车忘了几区的怎么办 5e在游戏里卡住不动怎么办 别人发的cdr文件打开太慢怎么办