理解C++20 Coroutine: Promise type

理解C++20 Coroutine: promise type

这是我学习 C++20 Coroutine 笔记的第三篇,打算做成一个系列,如果感兴趣可以从头开始读:

  1. 理解 C++20 Coroutine: 协程的概念
  2. 理解C++20 Coroutine: co_await与Awaiter

本文的主要参考文献是 C++ Coroutines: Understanding the promise type,建议直接读原文而不是我的笔记。

Promise objects

前面的笔记提到过,Promise object 是用来控制协程的行为的。

promise type 的实例 promise object 会在每次调用协程时被创建。然后编译器会为协程函数的调用生成一些代码,在协程执行到特定点时调用 promise 特定的函数。

举例说明,假设有一个协程被调用之后,创建了一个 promise object: promise,那么对于这个协程的调用可能会是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
co_await promise.initial_suspend();
try
{
<body-statements>
}
catch (...)
{
promise.unhandled_exception();
}
FinalSuspend:
co_await promise.final_suspend();
}

其中 <body-statements> 是协程的代码。

相比于普通函数调用,协程的调用会额外多一些步骤:

  1. 通过 operator new 分配协程帧(coroutine frame);(可选步骤)
  2. 复制所有函数参数到协程帧;
  3. 调用 promise type 的构造函数,得到 promise object: promise。
  4. 调用 promise.get_return_object() 来获取协程第一次返回时的返回结果;
  5. 调用 promise.initial_suspend() 并 co_await 它的返回结果;
  6. 当 co_await promise.initial_suspend() 恢复执行(resume),你所编写的协程主体代码开始执行。

当执行遇到 co_return 时执行的一些额外的步骤:

  1. 调用 promise.return_void() 或 promise.return_value(<expr>);
  2. 以创造它们的反向顺序销毁所有局部变量;
  3. 调用 promise.final_suspend() 并 co_await 它的返回结果;

或者,执行因为遇到未处理异常而退出,那么:

  1. 捕获该异常并在 catch-block 调用 promise.unhandled_exception();
  2. 调用 promise.final_suspend() 并 co_await 它的返回结果;

一旦协程主体的代码执行完毕,协程帧就会被销毁,销毁步骤包括:

  1. 调用 promise object 的析构函数;
  2. 调用函数参数副本的析构函数;
  3. 调用 operator delete 来释放协程帧的内存;(可选步骤)
  4. 将执行迁移回 caller/resumer;

分配协程帧

传递给 operator new 的大小不是 sizeof(promise type),而是整个协程帧的大小,包括所有函数参数的大小、promise object 的大小、局部变量的大小和编译器用于管理协程状态的内存大小。

作为一种优化,编译器可能不会使用 operator new 分配协程帧。

  1. it is able to determine that the lifetime of the coroutine frame is strictly nested within the lifetime of the caller; and
  2. the compiler can see the size of coroutine frame required at the call-site.

在这种情况下,编译器可以将协程帧分配到调用者的激活帧中(栈帧(stack-frame) 或 协程帧(coroutine-frame) 都有可能)。

promise type 可以定义 operator new() 的重载来替换全局的 operator new。

复制参数到协程帧

如果参数以值传递的方式(pass-by-value),那么会调用移动构造函数复制到协程帧。

如果参数以引用传递的方式(pass-by-reference),无论是左值还是右值,都只有引用会被复制到协程帧。

构造 promise object

在复制所有参数到协程帧之后会构建 promise object,这种设计允许你在构造 promise object 时(构造函数内)可以访问这些参数。

获取返回对象

成功构造 promise object 后的第一件事情就是通过调用 promise.get_return_object() 获取返回对象。

作者

uint128.com

发布于

2022-03-02

更新于

2022-05-18

许可协议

评论