..

C++20_coroutine

参考

  1. https://medium.com/@threehappyer/deep-dive-into-c-20-coroutines-ef5a557d15cb
  2. https://itnext.io/c-20-coroutines-complete-guide-7c3fc08db89d

概念相关

协程的关键字

  1. co_return 用于指定协程的返回值,并且销毁协程的资源,并且在此以后协程不可重入。
  2. co_await 用于暂停协程,直到满足等待的条件。
  3. co_yield 用于向协程的调用者生成一个值,并暂时挂起协程。

协程的核心组件

  • Coroutine handle:这是指向协程状态的指针,可用于恢复或销毁协程。
  • Promise type of the coroutine:这是一个用户定义的类型,用于定义协程的返回对象暂停恢复的逻辑以及如何处理返回值异常
  • Awaiter:这是一个对象,用于定义协程在遇到co_await时的行为。awaiter 必须实现三个方法:await_readyawait_suspendawait_resume

协程的生命周期(流程)

  1. Creation(创建协程帧):用协程函数时,会创建一个协程帧,这是一个包含协程状态的数据结构。
  2. Initial Suspension(初始暂停):协程开始执行,但在运行任何实际代码之前,它会询问 promise 类型是否立即暂停。
  3. Execution(执行):协程开始执行用户定义的代码,直到遇到co_awaitco_yieldco_return
  4. Suspension(暂停):当协程遇到co_awaitco_yield时,它会根据等待者的逻辑暂停。
  5. Resumption(恢复):协程可以在稍后的某个时间点恢复,继续执行,直到下一个暂停点或完成。
  6. Final Suspension(最终暂停):程执行完毕后,询问 promise 类型是否再次暂停。
  7. Destruction(释放):协程帧被破坏,释放所有资源。

一些简单理解

协程函数

当一个函数里带有关键字co_awaitco_yieldco_return,这个函数就是协程函数。

协程的简单运行流程

参考链接代码https://github.com/shecannotsee/she_cpp_samples/blob/master/coroutines_test/t1_easy_sample.h

运行结果https://godbolt.org/z/szdxYcnd1

可以知晓,其实就是跟普通函数一样的调用流程。调用,然后获得返回值。

代码示例流程如下:

  1. main函数调用协程函数;
  2. 自动调用promise_type中的get_return_object函数,此时返回一个frame(函数帧),可以理解为函数暂停时的状态,以后可以拿着这个状态再去恢复到当前运行的状态;
  3. 自动调用promise_typeinitial_suspend函数,函数返回的时候调用返回结构中的await_ready(此时返回true)和await_resume
  4. 由于await_ready返回的是true,表示协程函数在初始化的时候不进行暂停,然后继续执行协程函数里的内容,开始执行协程函数;
  5. 遇到了co_return关键字,意味着协程函数结束了,调用promise_type中的return_void函数;
  6. 由于协程函数已经退出了,进行函数帧的资源回收,调用promise_type中的final_suspend函数,然后调用返回结构体中的await_ready(此时返回true)和await_resume
  7. main函数执行结束,执行frame的析构;

协程的完整运行流程

参考链接代码https://github.com/shecannotsee/she_cpp_samples/blob/master/coroutines_test/t4_demo.h

运行结果https://godbolt.org/z/PcT3PY1ar

代码示例流程如下:

  1. [line:90-93]main调用协程函数;
  2. 自动调用promise_type中的get_return_object函数;
  3. 自动调用promise_typeinitial_suspend函数,此时返回的结构体是std::suspend_always,由于其中await_ready的实现,会暂停协程point-1(初始化暂停,暂停在协程函数入口),然后把控制权交给main,协程不再继续执行;
  4. [line:94]main继续执行,此时value的值是还是coro_packagepromise_type中初始化的int也就是0;
  5. [line:95]main调用coro_packageresume()接口,让协程继续执行,此时协程会从上一个暂停点point-1继续执行;
  6. [line:83]从point-1继续执行,遇到第一个co_await关键字,此时根据co_await的返回,协程暂停,暂停点为point-2(为co_await结束的语句),交出控制权,让main继续执行;
  7. [line:96-98]main继续执行,此时value仍然没有改变,来到第二个coro_packageresume()接口,此时main不再执行,让协程继续执行,协程从上一个暂停点point-2继续执行。
  8. [line:84]协程继续执行,遇到关键字co_yield并且传入的是一个__LINE__参数,此时会调用promise_type里的yield_value接口,__LINE__参数作为yield_value的入参传入,此时value_的值被改变了。然后根据yield_value的返回值,协程暂停了,暂停点为point-3(为第一个co_yield结束的语句),让main继续执行。
  9. [line:99-101]main继续执行,此时value被改变,输出是84,然后来到第三个coro_packageresume()接口,此时main不再执行,让协程继续执行,协程从上一个暂停点point-3继续执行。
  10. [line:85]协程序流程参考步骤8,暂停点为point-4(为第二个co_yield结束的语句)
  11. [line:102-104]main继续执行,输出85,然后根据coro_packageresume()接口进到协程;
  12. [line:86]运行知道看到co_return关键字,此时调用coro_packagepromise_typereturn_value函数,传入参数也是co_return指定的参数,接下来自动调用coro_packagepromise_typefinal_suspend函数,然后根据函数返回的值继续将协程暂停,将控制权交给main;
  13. [line:105-107]main继续执行,此时value_被改编成co_return的值了,输出86,然后进入coro_packageresume()接口;
  14. [line:23-24]由于之前已经通过co_return已经结束了协程的声明周期,所以此时resume()中的coroutine_handle_.done()返回true,也就是意味着协程的工作已经完成并且退出,在退出的情况下如果还要通过coroutine_handle_.resume()恢复协程的运行,则会出现段错误,此时coro_packageresume()函数返回true;
  15. [line:108-110]mian运行结束

注意事项

协程帧

在处理协程函数返回时,声明的特定结构体promise内必须有一个promise_type类,然后里面必须拥有以下函数

  • auto get_return_object()函数通常返回一个特定结构体promise:可以参考协程的完整运行流程中的视线;
  • auto initial_suspend()函数返回特定结构体awaiter:调用协程函数的时候触发;
  • auto final_suspend()函数返回特定结构体awaiterco_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的时候,协程函数不暂停,继续执行,直到遇到下一个协程的关键字。