..
C++20_coroutine
参考
- https://medium.com/@threehappyer/deep-dive-into-c-20-coroutines-ef5a557d15cb
- https://itnext.io/c-20-coroutines-complete-guide-7c3fc08db89d
概念相关
协程的关键字
co_return用于指定协程的返回值,并且销毁协程的资源,并且在此以后协程不可重入。co_await用于暂停协程,直到满足等待的条件。co_yield用于向协程的调用者生成一个值,并暂时挂起协程。
协程的核心组件
- Coroutine handle:这是指向协程状态的指针,可用于恢复或销毁协程。
- Promise type of the coroutine:这是一个用户定义的类型,用于定义协程的返回对象、暂停和恢复的逻辑以及如何处理返回值和异常。
- Awaiter:这是一个对象,用于定义协程在遇到
co_await时的行为。awaiter 必须实现三个方法:await_ready、await_suspend和await_resume。
协程的生命周期(流程)
- Creation(创建协程帧):用协程函数时,会创建一个协程帧,这是一个包含协程状态的数据结构。
- Initial Suspension(初始暂停):协程开始执行,但在运行任何实际代码之前,它会询问 promise 类型是否立即暂停。
- Execution(执行):协程开始执行用户定义的代码,直到遇到
co_await、co_yield或co_return。 - Suspension(暂停):当协程遇到
co_await或co_yield时,它会根据等待者的逻辑暂停。 - Resumption(恢复):协程可以在稍后的某个时间点恢复,继续执行,直到下一个暂停点或完成。
- Final Suspension(最终暂停):程执行完毕后,询问 promise 类型是否再次暂停。
- Destruction(释放):协程帧被破坏,释放所有资源。
一些简单理解
协程函数
当一个函数里带有关键字co_await、co_yield、co_return,这个函数就是协程函数。
协程的简单运行流程
参考链接代码https://github.com/shecannotsee/she_cpp_samples/blob/master/coroutines_test/t1_easy_sample.h
运行结果https://godbolt.org/z/szdxYcnd1
可以知晓,其实就是跟普通函数一样的调用流程。调用,然后获得返回值。
代码示例流程如下:
- main函数调用协程函数;
- 自动调用
promise_type中的get_return_object函数,此时返回一个frame(函数帧),可以理解为函数暂停时的状态,以后可以拿着这个状态再去恢复到当前运行的状态; - 自动调用
promise_type中initial_suspend函数,函数返回的时候调用返回结构中的await_ready(此时返回true)和await_resume; - 由于
await_ready返回的是true,表示协程函数在初始化的时候不进行暂停,然后继续执行协程函数里的内容,开始执行协程函数; - 遇到了
co_return关键字,意味着协程函数结束了,调用promise_type中的return_void函数; - 由于协程函数已经退出了,进行函数帧的资源回收,调用
promise_type中的final_suspend函数,然后调用返回结构体中的await_ready(此时返回true)和await_resume; - main函数执行结束,执行frame的析构;
协程的完整运行流程
参考链接代码https://github.com/shecannotsee/she_cpp_samples/blob/master/coroutines_test/t4_demo.h
运行结果https://godbolt.org/z/PcT3PY1ar
代码示例流程如下:
- [line:90-93]main调用协程函数;
- 自动调用
promise_type中的get_return_object函数; - 自动调用
promise_type中initial_suspend函数,此时返回的结构体是std::suspend_always,由于其中await_ready的实现,会暂停协程point-1(初始化暂停,暂停在协程函数入口),然后把控制权交给main,协程不再继续执行; - [line:94]main继续执行,此时value的值是还是
coro_package里promise_type中初始化的int也就是0; - [line:95]main调用
coro_package的resume()接口,让协程继续执行,此时协程会从上一个暂停点point-1继续执行; - [line:83]从point-1继续执行,遇到第一个
co_await关键字,此时根据co_await的返回,协程暂停,暂停点为point-2(为co_await结束的语句),交出控制权,让main继续执行; - [line:96-98]main继续执行,此时value仍然没有改变,来到第二个
coro_package的resume()接口,此时main不再执行,让协程继续执行,协程从上一个暂停点point-2继续执行。 - [line:84]协程继续执行,遇到关键字
co_yield并且传入的是一个__LINE__参数,此时会调用promise_type里的yield_value接口,__LINE__参数作为yield_value的入参传入,此时value_的值被改变了。然后根据yield_value的返回值,协程暂停了,暂停点为point-3(为第一个co_yield结束的语句),让main继续执行。 - [line:99-101]main继续执行,此时value被改变,输出是84,然后来到第三个
coro_package的resume()接口,此时main不再执行,让协程继续执行,协程从上一个暂停点point-3继续执行。 - [line:85]协程序流程参考步骤8,暂停点为point-4(为第二个co_yield结束的语句)
- [line:102-104]main继续执行,输出85,然后根据
coro_package的resume()接口进到协程; - [line:86]运行知道看到
co_return关键字,此时调用coro_package里promise_type的return_value函数,传入参数也是co_return指定的参数,接下来自动调用coro_package里promise_type的final_suspend函数,然后根据函数返回的值继续将协程暂停,将控制权交给main; - [line:105-107]main继续执行,此时
value_被改编成co_return的值了,输出86,然后进入coro_package的resume()接口; - [line:23-24]由于之前已经通过
co_return已经结束了协程的声明周期,所以此时resume()中的coroutine_handle_.done()返回true,也就是意味着协程的工作已经完成并且退出,在退出的情况下如果还要通过coroutine_handle_.resume()恢复协程的运行,则会出现段错误,此时coro_package的resume()函数返回true; - [line:108-110]mian运行结束
注意事项
协程帧
在处理协程函数返回时,声明的特定结构体promise内必须有一个promise_type类,然后里面必须拥有以下函数
auto get_return_object()函数通常返回一个特定结构体promise:可以参考协程的完整运行流程中的视线;auto initial_suspend()函数返回特定结构体awaiter:调用协程函数的时候触发;auto final_suspend()函数返回特定结构体awaiter:co_return的时候触发;void unhandled_exception():用来处理协程中出现的异常;auto yield_value(...)函数返回特定结构体awaiter:在co_yield关键字的时候调用,入参就是关键字后面给的的参数;void return_value(...)orvoid return_void():在co_return关键字的时候调用,入参就是关键字后面给的参数;
对于某些函数的特定结构体awaiter返回,可以参考std::suspend_always以及std::suspend_never的实现并进行一系列的测试。
struct suspend_always {
bool await_ready() const noexcept { return false; }
void await_suspend(coroutine_handle<>) const noexcept {}
void await_resume() const noexcept {}
};
struct suspend_never {
bool await_ready() const noexcept { return true; }
void await_suspend(coroutine_handle<>) const noexcept {}
void await_resume() const noexcept {}
};
在每次返回std::suspend_always的时候,协程函数总是暂停,不再往下运行,并且将控制权交回给调用者,也就是调用协程的那一方;在每次返回std::suspend_never的时候,协程函数不暂停,继续执行,直到遇到下一个协程的关键字。