普通函数
一次调用一次返回.
协程
多次调用多次返回,且协程
有状态,返回值不一样.组织自己
的任务调试器,类似软中断
.
这里有粗略参考
C++20协程
特点
| 作用 |
| 管理协程周期 |
| 配置( |
| 定义挂起点,交换数据. |
我们重点理解协待/协中
.
用 名字空间 标;
构 可恢复事情
{
构 承诺类型
{
可恢复事情 取中对象()
{
中 可恢复事情(协程句柄<承诺类型>::从承诺(*本));
}
动 初始挂起(){中 从不挂起{};}
动 止挂起(){中 从不挂起{};}
空 中空(){}
};
协程句柄<承诺类型>_协程=空针;
可恢复事情()=默认;
可恢复事情(可恢复事情 常&)=删;
可恢复事情&符号=(可恢复事情 常&)=删;
可恢复事情(可恢复事情&&其他)
:_协程(其他._协程){
其他._协程=空针;
}
可恢复事情&符号=(可恢复事情&&其他){
如(&其他!=本){
_协程=其他._协程;
其他._协程=空针;
}
}
显 可恢复事情(协程句柄<承诺类型>协程):_协程(协程)
{
}
~可恢复事情()
{
如(_协程){_协程.消灭();}
}
空 恢复(){_协程.恢复();}
};
可恢复事情 计数器(){//协程
输出<<"调用计数器";
对(正 i=1;;i++)
{
协待 标::总是挂起{};
输出<<"恢复计数器"<<i<<")\n";
}
}
整 主()
{
输出<<"主调用";
可恢复事情 计数器=计数器();
输出<<"主恢复";
计数器.恢复();
计数器.恢复();
计数器.恢复();
计数器.恢复();
计数器.恢复();
输出<<"完成";
中 0;
}
要定义C++20
协程,要求返回类型
有个承诺类型
的子类型,
可恢复事情 计数器(){
__计数器环境*__环境=新 __计数器环境{};
__中=__环境->_承诺.取中对象();
协待 __环境->_承诺.初始挂起();
输出<<"调用计数器";
对(正 i=1;;i++)
{
协待 标::总是挂起{};
输出<<"恢复计数器"<<i<<")\n";
}
__止挂起标签:
协待 __环境->_承诺.止挂起();
}
要理解承诺类型
.
构 可恢复事情
{
构 承诺类型
{
可恢复事情 取中对象();
动 初始挂起(){中 从不挂起{};}
动 止挂起(){中 从不挂起{};}
空 中空(){}
};
计数器
中已加入取中对象,初始挂起,止挂起
等函数,__计数器环境
是编译器生成的上下文,保存协程挂起还原
的空间,由
__中=__环境->承诺.取中对象();
创建返回
对象.在执行协程
前,先用
协待 __环境->_承诺.初始挂起();
来判断是否挂起,上面返回从不挂起
,如果返回总是挂起
,则挂起该协程
.结束协程
前调用止挂起
,来判断结束前是否挂起
.
同样,对协中
,调用承诺
的返回空/返回值
,最后跳至协程尾
:
__环境->_承诺->中空();至 止挂起标签;
对协产
,类似协中
,如协产 "你好"
,翻译成:
协待 __环境->_承诺->产生值("你好");
可见协产
,就是协待
的语法糖
,调用承诺
的产生值
方法.如无该方法,则报错.从而承诺
起着内部控制协程,并传递(异常和结果)至外部系统
的作用.
用来外部
控制协程生命期
.
构 可恢复事情
{
协程句柄<承诺类型>_协程=空针;
~可恢复事情()
{
如(_协程){_协程.消灭();}
}
空 恢复(){_协程.恢复();}
};
下面为协程句柄
实现:
元<>构 协程句柄<空>{
常式 协程句柄()无异;
常式 协程句柄(空针型)无异;
协程句柄&符号=(空针型)无异;
常式 空*地址()常 无异;
常式 静 协程句柄 从地址(空*地址);
常式 显 符号 极()常 无异;
极 完成()常;
空 符号()();
空 恢复();
空 消灭();
私:
空*针;
};
每个承诺类型
,派生相应协程句柄<型>
的特化版.如协程句柄<可恢复事情::承诺类型>
:
元<型名 承诺>
构 协程句柄:协程句柄<空>
{
承诺&承诺()常 无异;
静 协程句柄 从承诺(承诺&)无异;
};
协程句柄
用于控制协程
的生命期
.如恢复/消灭/完成/操作符()()(用于初次执行)
.注意,不要再次释放协程
.
可恢复事情 取中对象()
{
中 可恢复事情(协程句柄<承诺类型>::从承诺(*本));
}
可通过该协程句柄
控制协程生命期
.
协程
通过协待
与可等待对象
来挂起协程
与同外界交换数据
.
协待 可等待;
//扩展为.
如(!可等待.准备好协()){//未准备好
//挂起点
可等待.挂起协(协程句柄);
//调用恢复点
}
可等待.恢复协();
可等待
主要由3个函数组成.准备好协
,为真
则不必等待,为假
则要
等待.不等待则恢复
,否则挂起
.挂起协
一般记录/设置
状态,而恢复协
可操作外部返回值
.
这样,使用者可定制挂起和恢复
.通过实现不同的可等待
来协程异步操作
.
还有其他可等待
对象:承诺类型::转换协/符号 协待()/可等待
.
协程
可实现单子(等待-挂起)/任务(等待)/生成器(挂起)/异步生成器(等待+产生)
.
管理任务/可等待机制(挂起点,交换数据)/回调机制(反馈)
,核心对象调度任务
:
用 协中函数=标::函数<空(常 协中对象*)>;
类 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>(等待对象));
加到立即跑(任务);
}
任务等待通知完成时(线标);
}
}
再用
宏来取恢复对象
.传递恢复对象
后,加协程
至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
优点:
| 优点 |
| 代码更精简. |
| 编译器可 |
| |
| 协程就是 |