..

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 对象的引用来生成一个协程句柄

协程的执行过程大致是这个样子的:

  1. 为协程调用分配一个协程帧,含协程调用的参数、变量、状态、promise 对象等所需的空间。
  2. 调用 promise.get_return_object(),返回值会在协程第一次挂起时返回给协程的调用者。
  3. 执行 co_await promise.initial_suspsend();根据上面对 co_await 语义的描述,协程可能在此第一次挂起(但也可能此时不挂起,在后面的协程体执行过程中挂起)。
  4. 执行协程体中的语句,中间可能有挂起和恢复;如果期间发生异常没有在协程体中处理,则调用 promise.unhandled_exception()。
  5. 当协程执行到底,或者执行到 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_waitco_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(表达式);