c++20协程上

原文
普通函数一次调用一次返回.
协程多次调用多次返回,且协程有状态,返回值不一样.组织自己的任务调试器,类似软中断.
这里有粗略参考
C++20协程特点

对象作用
协程句柄管理协程周期
承诺对象配置(启动挂起,执行结束前挂起等)行为,传递返回值
协待+可等待对象定义挂起点,交换数据.

我们重点理解协待/协中.

#包含<io流>
#包含<可恢复>

用 名字空间 标;

构 可恢复事情
{
  构 承诺类型
  {
    可恢复事情 取中对象()
    {
      中 可恢复事情(协程句柄<承诺类型>::从承诺(*));
    }
    动 初始挂起(){中 从不挂起{};}
    动 止挂起(){中 从不挂起{};}
    空 中空(){}
  };
  协程句柄<承诺类型>_协程=空针;
  可恢复事情()=默认;
  可恢复事情(可恢复事情 常&)=;
  可恢复事情&符号=(可恢复事情 常&)=;
  可恢复事情(可恢复事情&&其他)
    :_协程(其他._协程){
      其他._协程=空针;
    }
  可恢复事情&符号=(可恢复事情&&其他){(&其他!=){
      _协程=其他._协程;
      其他._协程=空针;
    }
  }
  显 可恢复事情(协程句柄<承诺类型>协程):_协程(协程)
  {
  }
  ~可恢复事情()
  {(_协程){_协程.消灭();}
  }
  空 恢复(){_协程.恢复();}
};

可恢复事情 计数器(){//协程
  输出<<"调用计数器";(正 i=1;;i++)
  {
    协待 标::总是挂起{};
    输出<<"恢复计数器"<<i<<")\n";
  }
}

整 主()
{
  输出<<"主调用";
  可恢复事情 计数器=计数器();
  输出<<"主恢复";
  计数器.恢复();
  计数器.恢复();
  计数器.恢复();
  计数器.恢复();
  计数器.恢复();
  输出<<"完成";0;
}

要定义C++20协程,要求返回类型有个承诺类型的子类型,

承诺类型

可恢复事情 计数器(){
  __计数器环境*__环境=新 __计数器环境{};
  __中=__环境->_承诺.取中对象();
  协待 __环境->_承诺.初始挂起();
  输出<<"调用计数器";(正 i=1;;i++)
  {
    协待 标::总是挂起{};
    输出<<"恢复计数器"<<i<<")\n";
  }
__止挂起标签:
  协待 __环境->_承诺.止挂起();
}

要理解承诺类型.

构 可恢复事情
{
  构 承诺类型
  {
    可恢复事情 取中对象();
    动 初始挂起(){中 从不挂起{};}
    动 止挂起(){中 从不挂起{};}
    空 中空(){}
  };

计数器中已加入取中对象,初始挂起,止挂起等函数,__计数器环境是编译器生成的上下文,保存协程挂起还原的空间,由

__中=__环境->承诺.取中对象();

创建返回对象.在执行协程前,先用

协待 __环境->_承诺.初始挂起();

来判断是否挂起,上面返回从不挂起,如果返回总是挂起,则挂起该协程.结束协程前调用止挂起,来判断结束前是否挂起.
同样,对协中,调用承诺返回空/返回值,最后跳至协程尾:

__环境->_承诺->中空();至 止挂起标签;

协产,类似协中,如协产 "你好",翻译成:

协待 __环境->_承诺->产生值("你好");

可见协产,就是协待语法糖,调用承诺产生值方法.如无该方法,则报错.从而承诺起着内部控制协程,并传递(异常和结果)至外部系统的作用.

协程句柄

用来外部控制协程生命期.

构 可恢复事情
{
  协程句柄<承诺类型>_协程=空针;
  ~可恢复事情()
  {(_协程){_协程.消灭();}
  }
  空 恢复(){_协程.恢复();}
};

下面为协程句柄实现:

<>构 协程句柄<>{
  常式 协程句柄()无异;
  常式 协程句柄(空针型)无异;
  协程句柄&符号=(空针型)无异;
  常式 空*地址()常 无异;
  常式 静 协程句柄 从地址(*地址);
  常式 显 符号 极()常 无异;
  极 完成();
  空 符号()();
  空 恢复();
  空 消灭();:*;
};

每个承诺类型,派生相应协程句柄<型>的特化版.如协程句柄<可恢复事情::承诺类型>:

<型名 承诺>
构 协程句柄:协程句柄<>
{
  承诺&承诺()常 无异;
  静 协程句柄 从承诺(承诺&)无异;
};

协程句柄用于控制协程生命期.如恢复/消灭/完成/操作符()()(用于初次执行).注意,不要再次释放协程.

可恢复事情 取中对象()
{
    中 可恢复事情(协程句柄<承诺类型>::从承诺(*));
}

可通过该协程句柄控制协程生命期.
协程通过协待可等待对象来挂起协程同外界交换数据.

协待 可等待;
//扩展为.(!可等待.准备好协()){//未准备好
    //挂起点
  可等待.挂起协(协程句柄);
    //调用恢复点
}
可等待.恢复协();

可等待主要由3个函数组成.准备好协,为则不必等待,为等待.不等待则恢复,否则挂起.挂起协一般记录/设置状态,而恢复协可操作外部返回值.
这样,使用者可定制挂起和恢复.通过实现不同的可等待协程异步操作.
还有其他可等待对象:承诺类型::转换协/符号 协待()/可等待.
协程可实现单子(等待-挂起)/任务(等待)/生成器(挂起)/异步生成器(等待+产生).

C++20的调度器

管理任务/可等待机制(挂起点,交换数据)/回调机制(反馈),核心对象调度任务:

用 协中函数=::函数<(常 协中对象*)>;

类 i预定任务
{//封装协程对象.
    友 类 调度器;:
    i预定任务()=;
    i预定任务(常 预定任务c++17&)=;
    i预定任务(64型 任务标识,调度器*管理者);~i预定任务();64型 取标识();
    虚 整 跑()=0;
    虚 极 是完成()=0;
    虚 协任务状态 取协状态()=0;
    空 绑定休息句柄(64型 句柄);
    等待模式 取等待模式();
    整 取等待超时();<型名 等待事件类型>
    动 绑定恢复对象(等待事件类型&&等待事件)->::允许如型<::是的基<恢复对象,等待事件类型>::>;<型名 等待事件类型>
    动 按类型取恢复对象()->::允许如型<::是的基<恢复对象,等待事件类型>::,等待事件类型*>;
    极 有恢复对象()常 无异;
    空 清理恢复对象();
    极 是上个调用下一()常 无异;
    极 是上个调用超时()常 无异;
    极 是上个调用失败()常 无异;
    空 加子任务(64型 线标);
    空 加等待通知任务(64型 线标);
    常 动&取子任务数组();
    常 动&取等待通知数组();
    空 终止();
    调度器*取管();
    静 i预定任务*当前任务();
    空 干产生(等待模式 模式,整 等待时间毫秒=0);
    空 置中函数(协中函数&&函数);
    空 确实中(常 协中对象&对象);
    空 确实中();
  保护:64型                    m任务标识;
    调度器*                    m管理者;::向量<64>       m子数组;::向量<64>       m等待通知数组;
    //用来从协程返回的值.
    等待模式             m等待模式=等待模式::等待闲着;
    整                   m等待超时=0;
    //用来发送至协程值,现在为异步事件.
    反射::用户对象        m恢复对象;
//异步等待,当成功执行`异步等待`时,向协程传递值64型                    m休息句柄=0;
    极                        m是终止=;
    协中函数            m协中函数;
};

类 预定任务c++20:公 i预定任务
{:
    预定任务c++20(64型 任务标识,协任务函数&&任务函数,调度器*管理者);
    ~预定任务c++20();
    整 跑();
    极 是完成()常 盖;
    协任务状态 取协状态()常 盖;
    空 绑定本到协任务();
    常 协恢复任务c++20&取恢复任务();
  保护:
    协恢复任务c++20            m协恢复任务;
//内部有`承诺类型`实现,通过它访问协程.
    协任务函数                m任务函数;
//存储函数对象.
};

我们保存了协程对象/相关函数对象,因为如果协程如果为λ,编译器不会维护λ生命期及λ捕捉的函数.
处理产生.

空 调度器::更新()
{
    r工作室分析器方法信息(s更新,"调度器::更新()",r工作室::分析器组类型::k逻辑工作);
    r工作室分析器自动区域(s更新);

    //先要干掉任务(!m需要干掉数组.空的())
    {
        动 线标=m需要干掉数组.();
        m需要干掉数组.();*临任务=按标识取任务(线标);(临任务!=空针)
        {
            消灭任务(临任务);
        }
    }

    //因为现在不执行下帧任务,而保留临时队列.
    推导(m开始任务帧)临帧任务;
    m开始任务帧.交换(临帧任务);(!临帧任务.空的())
    {
        动 任务标识=临帧任务.();
        临帧任务.();*任务=按标识取任务(任务标识);
        日志检查错误(任务);(任务)
        {
            加到立即跑(任务);
        }
    }
}

空 调度器::加到立即跑(i预定任务*预定任务)
{
    日志进程错误(预定任务);
    预定任务->();(预定任务->是完成())
    {
        消灭任务(预定任务);;
    }

    {
        动 等待模式=预定任务->取等待模式();
        动 等待超时毫秒=预定任务->取等待超时();
        用 r工作室::逻辑::等待模式;
        开关(预定任务->取等待模式())
        {
            若 从不等待:
                加到立即跑(预定任务);;
            若 等待下一帧:
                加到下一桢跑(预定任务);;
            若 等待无超时通知:
            若 用超时等待通知:
                {
                    处理等待通知任务(预定任务,等待模式,等待超时毫秒);
                };
            若 等待闲着:;
            默认:
                r工作室错误(不能跑在此错误());;
        }
    }
    退出0:;
}

任务->跑后,到达下个挂起点,外部代码根据挂起模式控制行为,主要有:从不等待,等待下一帧,等待无超时通知(外界通知后),用超时等待通知(通知或超时后),等待闲着(特殊,删任务)模式.
然后是恢复处理.
恢复通过向关联任务传递恢复对象来实现.

//不是真实事件通知,<型名 E>
动 按等待对象恢复任务(E&&等待对象)->::允许如型<::是的基<恢复对象,E>::>
{
    动 线标=等待对象.任务标识;(是任务在等待集(线标))
    {
        //仅可等待集中任务可恢复.*任务=按标识取任务(线标);(r工作室可能(任务!=空针))
        {
            任务->绑定恢复对象(::前向<E>(等待对象));
            加到立即跑(任务);
        }

        任务等待通知完成时(线标);
    }
}

再用

#定义 r协取恢复对象(恢复对象类型) r协本任务()->按类型取恢复对象<恢复对象类型>()

宏来取恢复对象.传递恢复对象后,加协程m读任务队列中,以便更新中唤醒它.下面为如何实现可等待:

类 r工作室应用服务接口 请求远调用
{:
    请求远调用()=;
    //构造=删
    ~请求远调用()=默认;

    请求远调用(常 逻辑::游戏服务调用者针&代理,常 标::串视 函数名,反射::实参&&,整 超时毫秒):
    m代理(代理)
        ,m函数名(函数名)
        ,m实参(::前向<反射::实参>())
        ,m超时毫秒(超时毫秒)
    {}
    极 准备好协()
    {
        中 假;
    }
    空 挂起协(协程句柄<>)常 无异
    {*任务=r协本任务();
        动 环境=::造共<服务环境>();
        环境->任务标=任务->取标识();
        环境->超时=m超时毫秒;
        动 实参=m实参;
        m代理->干动态调用(m函数名,::移动(实参),环境);
        任务->干产生(等待模式::等待无超时通知);
    }
    ::r工作室::逻辑::远调用恢复对象*恢复协()常 无异
    {
        中 r协取恢复对象(逻辑::远调用恢复对象);
    }:
    逻辑::游戏服务调用者针  m代理;::串                 m函数名;
    反射::实参             m实参;
    整                     m超时毫秒;
};

重点是准备好协/挂起协/恢复协的实现.有时需要完成协程时,发送通知或传递返回值或提供下步操作值.

任务->置中函数([,服务器,实体,命令头,路由器地址,请求头,环境](常 协中对象*对象){
    常 动*中对象=动转<常 协远调用中对象*>(对象);(r工作室可能(中对象))
    {
        干响应远调用(服务器,实体.(),路由器地址,&命令头,
      请求头,常转<服务环境&>(环境),
      中对象->远调用结果类型,中对象->总中,中对象->中值);
    }
});

通过λ绑定函数,利用协中承诺类型传递返回值.

协任务信息 心跳服务::干心跳(逻辑::调度器&调度器,整 测试值)
{
    中 调度器.创建任务20(
    [测试值]()->逻辑::协恢复任务c++20{
        协待 逻辑::协任务::休息(1000);
        打印格式("完成任务");
        协中 协远调用中对象(反射::(测试值+1));
    }
    );
}

最后用返回值设置回调.

空 协恢复任务c++20::承诺类型::返回值(常 协中对象&对象)
{*任务=r协本任务();
    任务->确实中(对象);
}

额外对象作为事件传递给业务.发起事件后,删除协程任务.示例如下:

动 客户代理=m远调用客户->创建服务代理("多在线.心跳");
m调度器.创建任务20([客户代理]()->r工作室::逻辑::协恢复任务c++20{*任务=r协本任务();
    打印格式("第1步,任务是%llu",任务->取标识());
    协待 r工作室::逻辑::协任务::下一帧{};
    打印格式("产生后,第2步");
    整 c=0;(c<5)
    {
        打印格式("当循环中%d",c);
        协待 r工作室::逻辑::协任务::休息(1000);
        c++;
    }(c=0;c<5;c++)
    {
        打印格式("对循环中%d",c);
        协待 r工作室::逻辑::协任务::下一帧{};
    }
    打印格式("第3步,%d",c);
    动 新任务标识=协待 r工作室::逻辑::协任务::创建任务(,[]()->逻辑::协恢复任务c++20{
        打印格式("子协程");
        协待 r工作室::逻辑::协任务::休息(2000);
        打印格式("休息后");
    });
    打印格式("协程中新任务%d",新任务标识);
    打印格式("开始等待任务");
    协待 r工作室::逻辑::协任务::等待完成任务{新任务标识,10000};
    打印格式("等待后");

    r工作室::逻辑::协任务::请求远调用 远调用请求{客户代理,"干心跳",r工作室::反射::实参{3},5000};*远调用中=协待 远调用请求;(远调用中->远调用结果类型==r工作室::网络::远调用响应结果类型::请求下一)
    {
        断定(远调用中->总中==1);
        动 返回值=远调用中->中值.<>();
        断定(返回值==4);
        打印格式("成功,值为%d",返回值);
    }{
        打印格式("失败,值为%d",()远调用中->远调用结果类型);
    }

    协待 r工作室::逻辑::协任务::休息(5000);
    打印格式("休息5秒后,第4步");
    协中 r工作室::逻辑::协无效;
});
/*
step1: task is 1
step2 after yield!
in while loop c=0
in while loop c=1
in while loop c=2
in while loop c=3
in while loop c=4
in for loop c=0
in for loop c=1
in for loop c=2
in for loop c=3
in for loop c=4
step3 5
new task create in coroutine: 2
Begin wait for task!
from child coroutine!
after child coroutine sleep
After wait for task!
service yield call finish!
rpc coroutine run suc, val = 4!
step4, after 5s sleep
*/

对比C++17优点:

序号优点
1代码更精简.
2编译器可自动处理栈变量.
3协待可直接返回值,且有强制类型约束.
4协程就是λ,可充分利用λ.
posted @ 2021-09-28 21:31  zjh6  阅读(49)  评论(0)    收藏  举报  来源