..
C++20协程相关
co_await、co_yield、co_return 和协程控制
在讨论该如何定义 uint64_resumable 之前,我们需要先讨论一下协程的这三个新关键字。
首先是 co_await。对于下面这样一个表达式:
auto result = co_await 表达式;
编译器会把它理解为:
auto&& __a = 表达式;
if (!__a.await_ready()) {
__a.await_suspend(协程句柄);
// 挂起/恢复点
}
auto result = __a.await_resume();
也就是说,“表达式”需要支持 await_ready、await_suspend 和 await_resume 三个接口。如果 await_ready() 返回真,就代表不需要真正挂起,直接返回后面的结果就可以;否则,执行 await_suspend 之后即挂起协程,等待协程被唤醒之后再返回 await_resume() 的结果。这样一个表达式被称作是个 awaitable。
标准里定义了两个 awaitable,如下所示:
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 {}
};
也就是说,suspend_always 永远告诉调用者需要挂起,而 suspend_never 则永远告诉调用者不需要挂起。两者的 await_suspend 和 await_resume 都是平凡实现,不做任何实际的事情。一个 awaitable 可以自行实现这些接口,以定制挂起之前和恢复之后需要执行的操作。
上面的 coroutine_handle 是 C++ 标准库提供的类模板。这个类是用户代码跟系统协程调度真正交互的地方,有下面这些成员函数我们等会就会用到:
- destroy:销毁协程done:判断协程是否已经执行完成
- resume:让协程恢复执行
- promise:获得协程相关的 promise 对象(和[第 19 讲] 中的“承诺量”有点相似,是协程和调用者的主要交互对象;一般类型名称为 promise_type)
- from_promise(静态):通过 promise 对象的引用来生成一个协程句柄
协程的执行过程大致是这个样子的:
- 为协程调用分配一个协程帧,含协程调用的参数、变量、状态、promise 对象等所需的空间。
- 调用 promise.get_return_object(),返回值会在协程第一次挂起时返回给协程的调用者。
- 执行 co_await promise.initial_suspsend();根据上面对 co_await 语义的描述,协程可能在此第一次挂起(但也可能此时不挂起,在后面的协程体执行过程中挂起)。
- 执行协程体中的语句,中间可能有挂起和恢复;如果期间发生异常没有在协程体中处理,则调用 promise.unhandled_exception()。
- 当协程执行到底,或者执行到 co_return 语句时,会根据是否有非 void 的返回值,调用 promise.return_value(…) 或 promise.return_void(),然后执行 co_await promise.final_suspsend()。
用代码可以大致表示如下:
frame = operator new(…);
promise_type& promise =
frame->promise;
// 在初次挂起时返回给调用者
auto return_value =
promise.get_return_object();
co_await promise
.initial_suspsend();
try {
执行协程体;
可能被 co_wait、co_yield 挂起;
恢复后继续执行,直到 co_return;
}
catch (...) {
promise.unhandled_exception();
}
final_suspend:
co_await promise.final_suspsend();
上面描述了 co_await 和 co_return,那 co_yield 呢?也很简单,co_yield 表达式 等价于:
co_await promise.yield_value(表达式);