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 @   zjh6  阅读(42)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示