当前位置: 首页 > 科技观察

C++20协程剖析

时间:2023-03-13 18:03:39 科技观察

下面来分析一下协程的流程。通过这次分析,希望梳理一下协程的几个重要概念之间的关系,把这几个点联系起来。所以在概念参考中我们列出了相应的概念文本。协程的创建在启动C++20协程之前,将创建一个新的协程状态(协程状态)。然后构造协程的promise对象(promise)。承诺对象(promise)通过get_return_object()构造协程的返回值结果。当协程第一次挂起时,这个返回值被分配给调用者。然后通过co_awaitpromise.initial_suspend(),确定初始测试完成后协程的行为。如果返回std::suspend_always,则暂停初始化,如果返回std::suspend_never,则在初始化后继续执行。(注意initial_suspend也可以返回其他协程体)协程的co_awaitcw_ret=co_awaitawaiter或cw_ret=co_awaitfun(),先计算表达式fun,fun返回结果,是一个等待体awaiter。系统首先调用awaiter.await_ready()接口查看等待体是否就绪,如果未就绪则调用awaiter.await_suspend()(返回false)。await_suspend根据参数记录调用它的协程句柄。await_suspend的返回值是returntrue,或者returnvoid会暂停协程。之后,如果外部恢复执行协程,则调用awaiter.await_resume()接口。它将结果作为co_await的返回值返回。协程的co_yieldco_yieldcy_ret;相当于调用co_waitpromise.yield_value(cy_ret),在yield_value中记录参数cy_ret后即可使用,如果yield_value的返回值为std::suspend_always,协程被挂起,如果返回std::suspend_never,协程继续运行。co_returnco_yieldcr_ret;的协程,调用promise.retun_value(cr_ret),如果没有等价于promise.retun_viod()的返回值,可以在return_value中记录参数cr_ret后使用。然后调用co_awaitpromise.final_suspend(void),如果返回值为std::suspend_always,需要手动清理协程句柄并调用handle.destroy()。这里有个问题,final_suspend,并没有真正挂起协程。查看C++参考,其中还说调用promise.final_suspend()和co_waits结果。。据说如果返回,应该暂停。但是用VS2022测试不会suspend,再去探查C++20协程文章如果返回std::suspend_always,需要自己清理协程句柄。对此感到怀疑。概念参考附录:这些概念包含在原文第一章中,这里的附录只是为了方便大家。协程状态(coroutinestate)是协程启动时,新的空间存放协程状态,协程状态记录了协程函数的参数,协程的运行状态,变量。挂断点。注意协程状态(coroutinestate)并不是协程函数的返回值RET。虽然我们设计的RET一般也有promise和协程句柄,但是大家一般都是用RET来操作协程的回收,获取返回值。但是协程状态理论上应该也包含协程运行参数、断点等信息。协程状态应该是协程句柄对应的一段数据,由系统管理。承诺对象(promise)承诺对象的表达形式必须是result::promise_type,result是协程函数的返回值。Promise对象是几个接口的实现,用来辅助协程,构造协程函数的返回值;提交并传递co_yield和co_return的返回值。指定协程启动阶段是否立即挂起;以及如何处理协程内部的异常。它的接口包括:autoget_return_object():用于生成协程函数的返回对象。autoinitial_suspend():用于明确协程函数初始化后的执行行为,返回值是awaiter,返回值是用co_wait调用的。std::suspend_always的返回值表示协程启动后立即挂起(不执行协程函数第一行代码),std::suspend_never的返回值表示协程不立即挂起开始后。(当然,既然是返回等待体,这里可以选择做什么样的等待操作)voidreturn_value(Tv):调用co_returnv后会调用该函数,可以保存co_return的结果autoyield_value(Tv):调用co_yield后会调用这个函数,可以保存co_yield的结果,其返回值为std::suspend_always,表示协程会被挂起。如果返回std::suspend_never,表示不会被挂起。autofinal_suspend()noexcept:协程退出时调用的接口,返回std::suspend_never,自动销毁协程状态对象。如果final_suspend返回std::suspend_always,用户需要调用handle.destroy()销毁它。但值得注意的是,返回std::suspend_always并不会挂起协程。前面我们提到,创建协程时,协程状态会是new的。您可以通过在promise_type中重载operatornew和operatordelete来使用自己的内存分配接口。(请参考RevisitingC++20Coroutine)协程句柄(coroutinehandle)是一个协程标记,用于操作协程回收和销毁句柄。协程句柄的表达形式为std::coroutine_handle,其模板参数为promise对象(promise)类型。handle有几个重要的功能:resume()函数可以恢复协程。done()函数可以判断协程是否已经完成。返回false表示协程还没有完成,还在pending。协程句柄和承诺对象可以相互转换。std::coroutine_handle::from_promise:这是一个静态函数,可以从promise对象(promise)中获取对应的handle。std::coroutine_handle::promise()函数可以从协程句柄coroutinehandle中得到对应的承诺对象(promise)等待者(awaiter)。co_wait关键字会调用一个等待者对象(awaiter)。该对象内部还有3个接口。根据接口co_wait决定做什么。boolawait_ready():等待体是否就绪,返回false,表示协程没有就绪,立即调用await_suspend。返回真,表示准备就绪。autoawait_suspend(std::coroutine_handle<>handle)如果要挂起,调用接口。其中handle参数是调用等待体的协程,其返回值有三种可能:void返回truebool,返回true立即挂起,返回false不挂起。返回一个协程句柄,立即恢复对应句柄的操作。autoawait_resume():协程挂起后恢复时调用的接口。返回值用作co_wait操作的返回值。服务员值得一章更详细,所以让我们就此打住并了解它有2个专业。std::suspend_never类,一种不挂起的专用等待体类型。std::suspend_always类,一种专门用于挂起的等待体类型。之前的很多接口都用到了这两个特化类,也可以理解为协程内部其实很多地方都用到了co_wait关键字。本章总结本章解释了协程启动的细节和3个关键字。你可以通过这些关键概念来整合协程状态(coroutinestate)、承诺对象(promise)、协程句柄(coroutinehandle)和等待者。参考文档C++20协程初探C++20协程再探(C++20)协程简介TheCoroutineinC++20PromiseofCoroutinesC++Coroutines:Understandingoperatorco_await