C++20协程
Coroutine of C++20
C++的协程是:
- 对称的。一个协程暂停后,可返回 caller 或恢复任意协程。
- 语言级特性。编译器知道你在使用协程。然而不比库强到哪里去。
- 无栈(Stackless) 的。没有独立运行时栈,无惧爆栈,调度成本低。
- 一个协程在被命令「暂停」时,会保证将数据和当前运行位置保存在堆内存(以便恢复现场),然后转移运行权。
实现协程间切换
co_await Transfer{}; |
缺点
- 除非编译器优化,每个协程都需要通过
operator new
来分配 frame:- 动态内存分配可能引发性能问题;
- 在嵌入式或异构(例如 GPU)环境下,缺乏动态内存分配能力,难以工作。
- 除非编译器优化,协程的可定制点太多,需要大量间接调用/跳转(而不是内联),同样引起性能问题。
- 目前,编译器通常难以内联协程;
- HALO 优化理论:P0981R0。
- 动态分配和间接调用的存在,导致协程暂时无法成为异步框架的最优方法。
- Debug 的体验风评不佳。
三个关键字
co_await
暂停协程co_yield
暂停协程同时返回一个值co_return
结束协程并返回一个值
co_await
- expr转化为awaitable对象
- 获得该awaitable对象
- 调用
awaiter.await_ready()
,如果是false,需要挂起当前协程;恢复caller/resumer - 调用
awaiter.await_resume()
,该结果是co_await expr
的结果
- If the coroutine was suspended in the co_await expression, and is later resumed, the resume point is immediately before the call to awaiter.await_resume().
co_yield
co_await promise.yield_value(expr) |
关联三个对象
Promise Object
- 协程内部对象,将结果或异常提交到该结构中
Coroutine Handle
- 协程外部操纵对象,用于恢复协程或者销毁协程
Coroutine State
- 将数据和当前运行位置保存在堆内存
协程运行流程
开始运行
使用
operator new
分配新的coroutine state
对象将所有参数拷贝到
coroutine state
调用
promise type
的构造函数获取返回对象( )并将结果保存在局部变量中。当协程第一次挂起时,该调用的结果将返回给调用者。在此步骤之前(包括此步骤)引发的任何异常都会传播回调用者,而不是放置在 Promise 中。
调用
promise.get_return_object()
同时保存结果在本地变量。当协程第一次挂起,结果会被传递给caller。任何发生的异常会传递给caller调用
promise.initial_suspend()
然后co_await
结果。典型的Promise type会返回一个std::suspend_always
或者std::suspend_never
当
co_await promis.initial_suspend()
恢复,开始执行协程中的结构
到达暂停点
如有必要,在隐式转换为协程的返回类型之后,将先前获得的返回对象返回给调用者/恢复者。
如果是
co_return expr
- 调用
promise.return_void()
- 如果
expr
是非void类型,调用promise.return_value(expr)
- 按照创建时的相反顺序销毁所有具有自动存储持续时间的变量。
- 调用
promise.final_suspend()
然后co_await
结果
- 调用
promise.final_suspend()
返回awaiterable
对象
例子
|
- 进入
main
函数 main
函数调用add_one_coroutine()
函数- 创建一个
task::promise_type
类型的承诺对象,假设为promise1
- 调用
promise1.get_return_object()
,构造task
类型的协程对象,获得task1
promise1.initial_suspend()
被调用,由于返回std::suspend_never
,因此协程继续执行- 进入函数
add_one_coroutine()
内部,执行co_await AddOneAwaitable(x)
- 构造
AddOneAwaitable
对象,假设为awaitable1
- 调用
awaitable1.await_ready()
,由于我们返回false
,因此协程立即暂停 - 调用
awaitable1.await_suspend()
,之后协程将控制交还给 main 函数 main
函数再次调用add_one_coroutine()
函数- 同 3~9,协程对象
task2
也做一样的事情,最后将控制交还给 main 函数 - main 函数继续做自己的事情
AddOne
阻塞结束,调用task1
的协程句柄的resume()
函数,task1
协程恢复。需要注意的是,这里task1
会被转移到AddOne
创建的线程中恢复执行,这一点我们后面再说- 调用
awaitable1.await_resume()
,并将该函数的返回值作为co_await AddOneAwaitable(x)
的返回值 - 继续执行
add_one_coroutine
剩下的内容 - 函数执行结束,调用
promise1.return_void()
,该函数与co_return
有关,我们后面再讲 - 调用
promise1.final_suspend()
,协程执行结束 - 等待
task2
的阻塞结束,然后类似 12~16 步骤完成task2
协程
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 LZY的Code生活!