..
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
的时候,协程函数不暂停,继续执行,直到遇到下一个协程的关键字。