【知乎】如何编写 C++ 20 协程(Coroutines)
基本概念
Coroutine
Coroutine $:=$ 含有 co_return
/co_await
/co_yield
的可暂停/唤醒的函数
Coroutine State
包含
- Promise object
- 函数参数 (copy by value)
- 当前暂停点の局部变量 local variables and temporaries whose lifetime spans the current suspension point.
- 当前暂停点の特征信息 some representation of the current suspension point, so that a resume knows where to continue, and a destroy knows what local variables were in scope
Coroutine Execution
Coroutines (C++20) # Execution - cppreference.com
协程执行顺序文字版
一个 coroutine 按以下顺序执行:
- 调用
operator new
分配 coroutine state auto promise = return_type::promise_type{ /* ... */ };
构造 Promise object 根据return_type::promise_type
3. 优先选择参数为函数参数の构造函数 4. 如果没有,则选用默认构造函数- auto internal_local_val = promise.get_return_object();
6.
return_object` 会在协程第一次暂停时返回给 caller 7. exception 返回给 caller,promise 不保留 exception - co_await promise.inital_suspend();
-
-> suspend_never: eager-started, 立刻开始 -
-> suspend_always: lazy-started, 等待主动调用
coroutine_handle.resume()` 开始协程 - 继续执行函数体
- 遇到暂停点
- 执行 三大协程操作 中的两大
- caller/resumer 得到
static_cast<return_type>(return_object)
- 执行到
co_return expr;
- 执行
promise.return_xxx()
- .
decltype(expr) == void
$\to$ 执行promise.return_void()
- .
decltype(expr) != void
$\to$ 执行promise.return_value(expr)
- .
co_return;
$\to$ 1. - raii 倒序析构
promise.final_suspend();
--> suspend_never
: 立即销毁 --> suspend_always
: 等待主动调用coroutine_handle.destroy()
销毁协程
|
|
uncaught exception in coroutine
如果有 uncaught exception,则执行:
|
|
underfined behaviour 之 falling off end
- falling off end without
promise.return_void()
定义
|
|
- falling off end with return_type not
void
and without co keyword
- without co keyword $\to$ 不是协程,无论 return_type 是不是 Awaitable
- falling off end with return_type not
void
$\to$ 普通函数的 ub
|
|
dangling reference 之 协程 copy by value 传引用
todo
约束与类型
Awaitable
|
|
Promise
|
|
Coroutine Handle
|
|
三大协程操作
co_return
相当于协程的 return
。
协程完成所有 execution,除了 promise.final_suspend()
不再 suspend。
co_await
flowchart TD
COAWAIT[co_await awaitable;] --->|optional| TRANSFORM["promise.await_transform(awaitable)"]
TRANSFORM ---> COAWAIT
COAWAIT ---> AWAIT_READY{"awaitable.await_ready() -> bool"}
AWAIT_READY --->|true| CONTINUE["直接继续current_coroutine,避免不必要的 suspend"]
AWAIT_READY --->|false| SUSPEND["current_coroutine_handle.suspend()"]
SUSPEND ---> AWAIT_SUSPEND{"awaitable.await_suspend(current_coroutine_handle)"}
AWAIT_SUSPEND ---> AWAIT_SUSPEND_VOID(void)
AWAIT_SUSPEND_VOID ---> TRANSFER_CONTROL["返回控制权给 caller/resumer"]
AWAIT_SUSPEND ---> AWAIT_SUSPEND_BOOL(bool)
AWAIT_SUSPEND_BOOL --->|true| TRANSFER_CONTROL
AWAIT_SUSPEND_BOOL --->|false| RESUME_CURRENT["current_coroutine_handle.resume()"]
AWAIT_SUSPEND ---> AWAIT_SUSPEND_CORO(another_coroutine_handle)
AWAIT_SUSPEND_CORO ---> RESUME_ANOTHER["another_coroutine_handle.resume()"]
AWAIT_SUSPEND -...- AWAIT_SUSPEND_RESP["awaitable.await_suspend(cur_coro) 应处理好当前 coroutine 的 schedule 问题,如把 current coroutine handle 提交到一个 executor"]
co_yield
|
|
等价于
|
|
动态分配
C++ coroutine 作为无栈协程,默认使用 heap allocation (operator new/delete
)
相比 static alloc 会少内联优化机会,且无法在嵌入式环境工作
可以自定义 allocator 并重载 operator new
调用解决