C++20 - 协程
C++20引入了协程的概念,但是对于首次接触的开发者来说,理解起来还是有些吃力,本文尝试一次性讲通协程的基本概念以及用法。
- 为什么需要协程?
- 协程的基本概念
- C++20 协程的三个关键字
- 完整执行流程逐步解析
- 数据流转详解
- 7.1 协程帧(Coroutine Frame)
- 7.2 状态机变换
- 7.3 co_yield 的展开
- 7.4 co_await 的展开
- 7.5 co_return 的展开
- 内存管理与生命周期
- suspend_always 与 suspend_never 的本质
- 自定义 Awaiter 实战
- 协程 vs 线程 vs 回调
- 常见陷阱与注意事项
- 13.1 Task 的设计目标
- 13.2 continuation:连接两个协程的纽带
- 13.3 FinalSuspendAwaiter 详解
- 13.4 CoAwaitAwaiter 详解
- 13.5 两条执行路径
- 13.6 完整代码(含热启动和冷启动演示)
- 13.7 实际运行输出分析
- 全景脑图:所有概念的关系图
- FinalSuspendAwaiter vs CoAwaitAwaiter:如何区分?
1. 为什么需要协程?
传统迭代器的痛点
假设你要实现一个"按需产生数字"的迭代器,传统做法必须维护一个状态机:
// 传统方式:手写状态机,丑且容易出错
class Counter {
int current_, to_;
public:
Counter(int from, int to) : current_(from), to_(to) {}
bool has_next() const { return current_ <= to_; }
int next() { return current_++; }
};这还算简单。如果要生成一棵树的所有节点(DFS 遍历),传统方式需要手动管理一个栈,逻辑完全被状态管理代码淹没。
用协程,就可以把"生成逻辑"写得和普通函数一样自然:
Generator dfs(TreeNode* node) {
if (!node) co_return;
co_yield node->val;
for (auto child : node->children)
// 对子节点递归——这种写法用传统迭代器根本做不到
for (auto v : dfs(child)) // 配合 range-for 使用
co_yield v;
}异步回调地狱
// 传统回调地狱
readFile("a.txt", [](auto data) {
processData(data, [](auto result) {
writeFile("b.txt", result, [](auto ok) {
if (ok) notify([](){ /* ... */ });
});
});
});
// 用协程,像同步代码一样顺序书写
Task pipeline() {
auto data = co_await readFile("a.txt");
auto result = co_await processData(data);
auto ok = co_await writeFile("b.txt", result);
if (ok) co_await notify();
}2. 协程的基本概念
协程(Coroutine) 是一种可以在执行过程中主动挂起(suspend),并在之后某个时刻恢复(resume)的函数。
| 概念 | 普通函数 | 协程 |
|---|---|---|
| 调用方式 | ret = func(args) | handle = coro(args) |
| 执行模型 | 一次性执行完毕后返回 | 可多次挂起和恢复 |
| 栈帧管理 | 操作系统调用栈 | 堆上分配的协程帧 |
| 状态保存 | 无(函数返回即销毁) | 挂起时保存所有局部变量 |
| 返回值 | 函数返回时给出 | 挂起时可以"产出"中间值 |
关键直觉:协程在co_yield处暂停,就像函数在return处返回,但协程帧没有销毁——下次resume()时,从暂停处继续执行,所有局部变量完好如初。
3. C++20 协程的三个关键字
C++20 协程只引入了三个新关键字,它们是协程区别于普通函数的唯一语法标志:
co_yield expr
- 含义:产出一个值,然后挂起协程
- 等价变换:
co_await promise.yield_value(expr) - 典型用途:生成器(Generator),逐一产出值
Generator counter(int from, int to) {
for (int i = from; i <= to; ++i) {
co_yield i; // 产出 i,然后挂起
}
}co_return [expr]
- 含义:协程结束,可选地返回最终值
- 等价变换:调用
promise.return_value(expr)或promise.return_void(),然后跳转到 final_suspend - 典型用途:显式结束协程,或返回最终结果
Generator example() {
co_yield 1;
co_yield 2;
co_return; // 显式结束(也可以省略,函数尾部隐式执行)
}co_await expr
- 含义:等待一个异步操作,如果操作未就绪则挂起协程;操作完成后恢复
- 典型用途:异步任务(async/await 模式)
Task fetchData() {
auto result = co_await httpGet("https://api.example.com/data");
// httpGet 完成后才会执行这一行
std::cout << result;
}4. 协程的组成部件详解
以示例代码为基础,逐一解析每个部件:
┌─────────────────────────────────────────────────────┐
│ Generator │ ← 协程返回类型(你可以自由命名)
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ promise_type │ │ ← 承诺类型(名字固定)
│ │ │ │
│ │ get_return_object() │ │
│ │ initial_suspend() │ │
│ │ final_suspend() │ │
│ │ yield_value(v) │ │
│ │ return_void() / return_value(v) │ │
│ │ unhandled_exception() │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ std::coroutine_handle<promise_type> handle; │ ← 协程句柄
└─────────────────────────────────────────────────────┘4.1 协程返回类型(Coroutine Return Object)
官方术语:Coroutine Return Type / Coroutine Object
示例中的名字:Generator
这是协程函数的返回类型,由你自己定义,名字完全自由(叫 Task、Fiber、Lazy 都行)。
作用:
- 作为协程函数的返回值,让调用方持有对协程的控制权
- 必须包含一个名为
promise_type的内嵌结构体(这是 C++ 标准规定的固定名字) - 通常持有一个
coroutine_handle,用于控制协程的恢复和销毁 - 对外暴露用户友好的接口(如
next()、value())
// 一个最小化的协程返回类型骨架
struct MyCoroutine {
struct promise_type { /* ... */ }; // ← 必须叫 promise_type
std::coroutine_handle<promise_type> handle; // 持有句柄
// 用户调用的接口
bool next() { /* resume */ }
int value() { /* get result */ }
};它与 promise_type 的关系:编译器通过 promise_type 来构建 MyCoroutine 对象(调用 get_return_object()),所以 promise_type 先于 MyCoroutine 存在。
4.2 承诺类型(promise_type)
官方术语:Promise Type
名字:固定,必须叫 promise_type,是协程返回类型的内嵌结构体
promise_type 是协程的控制核心。编译器在协程帧中自动创建一个 promise_type 实例,并在协程生命周期的各个关键节点调用其成员函数。
你可以把 promise_type 理解为协程与外界沟通的协议接口:
| 成员函数 | 调用时机 | 必须/可选 | 说明 |
|---|---|---|---|
get_return_object() | 协程启动时,最先调用 | 必须 | 创建并返回协程对象(即 Generator 实例) |
initial_suspend() | get_return_object() 之后,协程体执行之前 | 必须 | 决定协程是否在启动时立即挂起 |
final_suspend() noexcept | 协程体执行完毕(包括 co_return)之后 | 必须 | 决定协程是否在结束时再次挂起 |
yield_value(v) | 每次 co_yield v 时 | 使用 co_yield 时必须 | 保存产出值,返回 Awaiter |
return_void() | co_return; 或函数体结束时 | 有一个即可 | 协程无返回值时调用 |
return_value(v) | co_return v; 时 | 有一个即可 | 协程有返回值时调用 |
unhandled_exception() | 协程体内有未捕获异常时 | 必须 | 处理异常,通常 std::terminate() 或存储异常 |
get_return_object() 详解
auto get_return_object() {
// from_promise(*this) 从 promise 对象反推出协程帧地址,构造句柄
return Generator{
std::coroutine_handle<promise_type>::from_promise(*this)
};
}执行时机:这是协程被调用时第一个执行的函数(甚至在协程体的第一行代码之前)。返回的对象就是协程函数返回给调用方的值。
注意:当 initial_suspend() 返回 suspend_always 时,get_return_object() 已经执行完毕,才开始挂起——也就是说调用方拿到 Generator 对象时,协程已经构建完成但尚未执行协程体。
initial_suspend() 详解
std::suspend_always initial_suspend() {
// 返回 suspend_always → 协程启动后立即挂起(懒惰启动)
return {};
}
// 或者
std::suspend_never initial_suspend() {
// 返回 suspend_never → 协程启动后立即执行(热启动)
return {};
}如果返回 suspend_always(推荐用于 Generator):
- 协程函数被调用时,先执行
get_return_object(),把Generator对象返回给调用方 - 然后挂起,等待调用方主动
resume() - 调用方有机会在协程真正开始执行前做一些准备工作
如果返回 suspend_never:
- 协程函数被调用时,立即开始执行协程体,直到遇到第一个
co_yield或co_return - 对于异步任务(
Task)通常选择这种方式,让任务立即开始执行
final_suspend() 详解
std::suspend_always final_suspend() noexcept {
// 协程体执行完毕后,再次挂起
// 这使得调用方可以安全地访问最终的 promise 状态
return {};
}注意:final_suspend() 必须标记为 noexcept,这是标准要求。
如果返回 suspend_always:
- 协程在结束后挂起,协程帧不会自动销毁
handle.done()返回true,调用方需要手动调用handle.destroy()或通过析构函数销毁
如果返回 suspend_never:
- 协程结束后自动销毁协程帧
- 危险:如果调用方仍然持有
coroutine_handle,则该句柄成为悬空指针! - 一般不推荐在
Generator中使用
4.3 协程句柄(coroutine_handle)
官方术语:Coroutine Handle
类型:std::coroutine_handle<PromiseType>
头文件:<coroutine>
coroutine_handle 是指向协程帧(Coroutine Frame)的指针,是控制协程生命周期的核心工具。
coroutine_handle<promise_type>
│
│ 指向堆上分配的协程帧
▼
┌─────────────────────────────────┐
│ 协程帧(Coroutine Frame)│
│ │
│ resume_fn ─────────────────► 恢复函数指针
│ destroy_fn ─────────────────► 销毁函数指针
│ promise_type ◄─────────────── 就在这里!
│ suspend_index (当前挂起点) │
│ 局部变量 from, to, i │
│ 临时的 awaiter 对象 │
└─────────────────────────────────┘主要操作
// 从 promise 对象构造句柄(在 get_return_object 里用到)
auto h = std::coroutine_handle<promise_type>::from_promise(promise_obj);
// 恢复协程执行(从挂起点继续)
h.resume();
// 等价写法
h();
// 销毁协程帧,释放内存
h.destroy();
// 检查协程是否已经执行到 final_suspend(即协程体已经跑完)
bool is_done = h.done();
// 获取 promise 对象的引用
promise_type& p = h.promise();
// 获取底层 void* 地址(可以用于跨类型转换)
void* addr = h.address();
auto h2 = std::coroutine_handle<promise_type>::from_address(addr);两种模板形式
// 有类型参数:知道 promise_type 具体类型,可以访问 promise()
std::coroutine_handle<Generator::promise_type> typed_handle;
// 无类型参数(type-erased):只能 resume/destroy/done,不能访问 promise
std::coroutine_handle<void> erased_handle;
// 有类型的可以隐式转为无类型的
erased_handle = typed_handle;4.4 等待体(Awaiter / Awaitable)
co_yield、co_await 都需要和一个等待体(Awaiter)配合工作。
Awaiter 是任何满足如下接口的类型:
struct MyAwaiter {
// 1. 是否已经就绪?如果返回 true,跳过挂起直接继续
bool await_ready();
// 2. 挂起时执行(返回 void 或 bool 或 coroutine_handle)
// handle 是当前被挂起的协程的句柄
// 用于控制挂起前,做一些自定义的事情
void await_suspend(std::coroutine_handle<> handle);
// 3. 恢复后执行,返回值就是 co_await 表达式的值
// 用于控制恢复后,做一些自定义的事情
ReturnType await_resume();
};await_ready() 的作用
bool await_ready() { return false; } // false = 需要挂起
bool await_ready() { return true; } // true = 不挂起,直接继续如果 await_ready() 返回 true,后续的 await_suspend() 不会被调用,协程直接继续执行。
await_suspend() 的三种返回值
// 1. void: 挂起当前协程,控制权返回给调用方/调度器
void await_suspend(std::coroutine_handle<> h) { /* 可以在这里存储 h,以便后续 resume */ }
// 2. bool: true=挂起,false=不挂起(立即恢复当前协程)
bool await_suspend(std::coroutine_handle<> h) { return should_suspend; }
// 3. coroutine_handle<>: 对称转移(symmetric transfer),立即恢复另一个协程
std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) {
return other_coroutine_handle; // 转移到另一个协程
}标准库提供的两个基本 Awaiter
// suspend_always: 永远挂起(await_ready 返回 false,await_suspend 什么也不做)
struct suspend_always {
constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};
// suspend_never: 永远不挂起(await_ready 返回 true,不会进入 await_suspend)
struct suspend_never {
constexpr bool await_ready() const noexcept { return true; }
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};Awaitable vs Awaiter
- Awaiter:直接实现了
await_ready/await_suspend/await_resume三个接口的类型 - Awaitable:可以转换为 Awaiter 的类型,通过
operator co_await()或promise.await_transform()转换
// Awaitable 通过 operator co_await 转换为 Awaiter
struct MyAwaitable {
MyAwaiter operator co_await() { return MyAwaiter{...}; }
};
// 或者 promise_type 里的 await_transform(可以拦截所有 co_await)
struct promise_type {
MyAwaiter await_transform(MyAwaitable a) { return MyAwaiter{a}; }
};5. 完整执行流程逐步解析
以示例代码 counter(1, 5) 为例,逐步追踪每一步发生了什么:
阶段 0:调用协程函数
auto gen = counter(1, 5);编译器不是直接执行 counter 的函数体,而是做以下事情:
Step 1: operator new(sizeof(__counterFrame))
↓ 在堆上分配协程帧,存储局部变量和协程状态
Step 2: 构造 promise_type(调用其构造函数)
↓
Step 3: 调用 promise.get_return_object()
↓ 这里创建了 Generator 对象,但还没有赋给 gen
↓ Generator 构造函数打印 "Generator()"
Step 4: 执行 co_await promise.initial_suspend()
↓ initial_suspend() 返回 suspend_always
↓ suspend_always::await_ready() 返回 false → 要挂起
↓ suspend_always::await_suspend(handle) 被调用(空操作)
↓ 协程挂起,__suspend_index = 1
Step 5: 控制权返回给调用方(main 函数)
↓ get_return_object() 返回的 Generator 对象赋给 gen输出:
Generator()
initial_suspend()此刻状态:
gen.handle指向一个有效的协程帧- 协程挂起在
initial_suspend之后,还没开始执行for循环
阶段 1:第一次 gen.next()
gen.next()
↓
handle.resume()协程从挂起点(initial_suspend 之后)恢复:
Step 1: __counterResume(__f) 被调用
switch(suspend_index=1) → goto __resume_counter_1
Step 2: __suspend_107_11.await_resume() — 对应 initial_suspend 的恢复
Step 3: 进入 for 循环,i=1
Step 4: 打印 "co_yield 1"
Step 5: 执行 co_yield 1
↓ 等价于: co_await promise.yield_value(1)
↓ yield_value(1) 打印 "yield_value(1)",保存 current_value=1,返回 suspend_always
↓ suspend_always::await_ready() 返回 false → 挂起
↓ __suspend_index = 2
↓ 返回到调用方
Step 6: handle.resume() 返回
handle.done() → false(还没到 final_suspend)
gen.next() 返回 true输出:
co_yield 1
yield_value(1)此刻状态:
- 协程挂起在
co_yield 1处 promise.current_value = 1i = 1,循环还在继续
阶段 2:调用 gen.value()
int v = gen.value();
// handle.promise().current_value → 1这只是读取 promise_type 中存储的值,不涉及任何协程状态变化。
阶段 3:后续 gen.next() 调用(i=2,3,4,5)
每次 gen.next() → handle.resume() → 从 __resume_counter_2(co_yield 之后)继续:
恢复点: __resume_counter_2
__suspend_112_9.await_resume() ← co_yield 的"恢复"部分,返回 void
i++ (i=2)
(下一轮循环)
co_yield 2 → 挂起,返回给调用方阶段 4:最后一次循环 i=5 之后
当 i=5 的 co_yield 5 恢复后,i++ 变为 6,循环条件 i <= to 不满足,退出循环:
Step 1: 循环结束,隐式执行 co_return(相当于 return_void)
promise.return_void() 打印 "return_void()"
Step 2: 跳转到 __final_suspend
Step 3: 执行 co_await promise.final_suspend()
final_suspend() 打印 "final_suspend()",返回 suspend_always
→ 挂起,__suspend_index = 3
Step 4: handle.resume() 返回
handle.done() → true(到达 final_suspend 且已挂起)
gen.next() 中 !handle.done() 为 false
gen.next() 返回 false输出:
co_yield 5
yield_value(5)
return_void()
final_suspend()阶段 5:gen 析构
// gen 离开作用域
~Generator():
打印 "~Generator()"
handle.destroy() → __counterDestroy(__f)
__f->~__counterFrame() (析构所有成员)
operator delete(__f) (释放堆内存)完整输出汇总
Generator() ← gen 对象构造完成
initial_suspend() ← 协程启动
co_yield 1 ← 第1次 next() 触发
yield_value(1)
Generated value: 1 ← main 打印
co_yield 2
yield_value(2)
Generated value: 2
co_yield 3
yield_value(3)
Generated value: 3
co_yield 4
yield_value(4)
Generated value: 4
co_yield 5
yield_value(5)
Generated value: 5
return_void() ← 最后一次 next() 触发,循环结束
final_suspend()
← gen.next() 返回 false,while 结束
~Generator() ← gen 析构6. 数据流转详解
co_yield 的数据流
协程内部 promise_type 外部(调用方)
─────────────────────────────────────────────────────────────────────────
co_yield 42;
│
│ 调用
▼
yield_value(42)
│ 保存
▼
current_value = 42
│ 返回 suspend_always
▼
协程挂起
handle.resume() 返回
gen.value()
│ 调用
▼
handle.promise().current_value
│ 读取
▼
42co_await 的数据流(以异步为例)
协程内部 Awaiter 外部事件/调度器
────────────────────────────────────────────────────────────────────
auto result = co_await asyncOp;
│
├─► await_ready() → false(未就绪,需要挂起)
│
├─► await_suspend(my_handle)
│ │ 保存 handle,注册回调
│ ▼
│ 协程挂起,控制权返回给调度器
│
│ [异步操作完成]
│ callback → my_handle.resume()
│
└─► await_resume() → 返回操作结果
│
▼
result = 异步操作的返回值7. 编译器内部变换揭秘
这是理解协程最重要的部分。编译器将协程函数变换为一个状态机。以下是基于 CoroutineTransform.cpp(由 cppinsights.io 生成)的详细解析。
7.1 协程帧(Coroutine Frame)
编译器为每个协程函数生成一个结构体来保存协程帧:
struct __counterFrame {
// 函数指针:resume 和 destroy 的实现
void (*resume_fn)(__counterFrame *);
void (*destroy_fn)(__counterFrame *);
// promise 对象(存储在帧内,不是堆上另一块内存)
Generator::promise_type __promise;
// 当前挂起点编号(状态机的状态)
int __suspend_index;
// 标记 initial_suspend 是否已经挂起过(用于异常处理)
bool __initial_await_suspend_called;
// 原始函数的参数(保存到帧里,因为函数调用栈会消失)
int from;
int to;
// 局部变量(保存到帧里)
int i;
// 各个挂起点的 awaiter 对象
std::suspend_always __suspend_107_11; // initial_suspend 的 awaiter
std::suspend_always __suspend_112_9; // co_yield 的 awaiter
std::suspend_always __suspend_107_11_1; // final_suspend 的 awaiter
};关键洞察:
- 协程的所有局部变量(
from,to,i)都被提升到帧结构体中 promise_type也内嵌在帧中,from_promise(*this)/from_address(frame)就是把 promise 地址和帧地址互相换算__suspend_index就是状态机的状态变量
7.2 状态机变换
原始协程函数入口变换为:
Generator counter(int from, int to) {
// 1. 分配协程帧
__counterFrame* __f = reinterpret_cast<__counterFrame*>(
operator new(sizeof(__counterFrame))
);
__f->__suspend_index = 0;
__f->__initial_await_suspend_called = false;
__f->from = std::forward<int>(from); // 保存参数
__f->to = std::forward<int>(to);
// 2. 构造 promise
new (&__f->__promise) Generator::promise_type{};
// 3. 设置函数指针
__f->resume_fn = &__counterResume;
__f->destroy_fn = &__counterDestroy;
// 4. 第一次执行(处理 initial_suspend)
__counterResume(__f);
// 5. 返回协程对象
return __f->__promise.get_return_object();
}Resume 函数(状态机核心):
void __counterResume(__counterFrame* __f) {
try {
// 状态机跳转表
switch(__f->__suspend_index) {
case 0: break; // 初次执行
case 1: goto __resume_counter_1; // initial_suspend 后恢复
case 2: goto __resume_counter_2; // co_yield 后恢复
case 3: goto __resume_counter_3; // final_suspend 后恢复(通常不会走到)
}
// ── initial_suspend 处理 ──
__f->__suspend_107_11 = __f->__promise.initial_suspend();
if (!__f->__suspend_107_11.await_ready()) {
__f->__suspend_107_11.await_suspend(handle_from_frame(__f));
__f->__suspend_index = 1;
__f->__initial_await_suspend_called = true;
return; // 挂起!
}
__resume_counter_1:
__f->__suspend_107_11.await_resume(); // initial_suspend 的恢复值(void)
// ── 原始协程体(for 循环)──
for (__f->i = __f->from; __f->i <= __f->to; ++__f->i) {
std::cout << "co_yield " << __f->i << std::endl;
// ── co_yield 处理 ──
__f->__suspend_112_9 = __f->__promise.yield_value(__f->i);
if (!__f->__suspend_112_9.await_ready()) {
__f->__suspend_112_9.await_suspend(handle_from_frame(__f));
__f->__suspend_index = 2;
return; // 挂起!
}
__resume_counter_2:
__f->__suspend_112_9.await_resume();
}
// ── 隐式 co_return(return_void)──
__f->__promise.return_void();
goto __final_suspend;
} catch (...) {
if (!__f->__initial_await_suspend_called) {
throw; // initial_suspend 之前的异常,直接抛出
}
__f->__promise.unhandled_exception();
}
__final_suspend:
// ── final_suspend 处理 ──
__f->__suspend_107_11_1 = __f->__promise.final_suspend();
if (!__f->__suspend_107_11_1.await_ready()) {
__f->__suspend_107_11_1.await_suspend(handle_from_frame(__f));
__f->__suspend_index = 3;
return; // 挂起(done() 将返回 true)!
}
__resume_counter_3:
__f->destroy_fn(__f); // 自动销毁
}状态转移图:
state=0
│ (首次执行)
▼
initial_suspend()
│ await_ready()=false
▼ 挂起 → state=1 → return
resume() →
state=1
│ goto __resume_counter_1
▼
await_resume() (initial_suspend 的返回值,void)
│
▼
for 循环开始
│
▼
yield_value(i)
│ await_ready()=false
▼ 挂起 → state=2 → return
resume() →
state=2
│ goto __resume_counter_2
▼
await_resume() (co_yield 的返回值,void)
│
▼ 继续循环
│ [循环结束]
│
▼
return_void()
│
▼
final_suspend()
│ await_ready()=false
▼ 挂起 → state=3 → return (done()=true)
destroy() →
state=3
│ __counterDestroy(__f)
▼
帧内存释放7.3 co_yield 的展开
// 源码
co_yield expr;
// 编译器等价展开
auto&& awaiter = promise.yield_value(expr);
if (!awaiter.await_ready()) {
awaiter.await_suspend(handle); // 传入当前协程的 handle
// ← 挂起点,函数返回(return)
// [稍后 resume() 被调用]
}
// ← 恢复点(通过 goto 跳回来)
awaiter.await_resume(); // co_yield 的"值"(通常为 void)7.4 co_await 的展开
// 源码
auto result = co_await awaitable;
// 编译器等价展开(简化版,忽略 await_transform)
auto&& awaiter = get_awaiter(awaitable); // 可能调用 operator co_await 或 await_transform
if (!awaiter.await_ready()) {
awaiter.await_suspend(handle);
// ← 挂起点
}
// ← 恢复点
auto result = awaiter.await_resume(); // co_await 表达式的值就是 await_resume() 的返回值7.5 co_return 的展开
// 源码(无返回值)
co_return;
// 等价于:
promise.return_void();
goto __final_suspend;
// 源码(有返回值)
co_return expr;
// 等价于:
promise.return_value(expr);
goto __final_suspend;8. 内存管理与生命周期
协程帧的分配
默认情况下,编译器使用全局 operator new 在堆上分配协程帧。
// 编译器生成的伪代码
void* frame_ptr = operator new(sizeof(__coroutine_frame));
// 等价于 malloc(sizeof(__coroutine_frame))自定义分配器:可以在 promise_type 中重载 operator new:
struct promise_type {
static void* operator new(std::size_t sz) {
// 使用内存池分配,避免频繁堆分配
return my_pool.allocate(sz);
}
static void operator delete(void* ptr, std::size_t sz) {
my_pool.deallocate(ptr, sz);
}
};HALO 优化(Heap Allocation eLision Optimization):如果编译器能确定协程的生命周期不超过调用方,则可能省略堆分配,在调用方的栈帧上分配协程帧。
完整生命周期图
counter(1,5) 被调用
│
▼
operator new() ─────────────────────────────────────────┐
构造 promise_type │
get_return_object() → Generator 对象 │
initial_suspend() → 挂起 │
│
← 返回给调用方 │ 协程帧
│ 存活
[多次 resume() / co_yield] │
│
return_void() / co_return │
final_suspend() → 挂起(done()=true) │
│
← 此时调用方可以知道协程已完成 │
│
~Generator() 析构 │
│ │
▼ │
handle.destroy() │
│ │
▼ │
~__counterFrame()(析构帧内所有成员) │
operator delete() ──────────────────────────────────────┘避免悬空句柄
// 危险:final_suspend 返回 suspend_never 时,handle 变悬空
struct BadPromise {
std::suspend_never final_suspend() noexcept { return {}; }
// 协程完成后帧被自动销毁,handle 失效!
};
// 安全:final_suspend 返回 suspend_always,由 RAII 对象统一销毁
struct SafeGenerator {
~SafeGenerator() {
if (handle) handle.destroy(); // RAII 析构时统一销毁
}
std::coroutine_handle<promise_type> handle;
};9. suspend_always 与 suspend_never 的本质
这两个是标准库提供的最简单的 Awaiter:
// 完整定义(来自标准库)
struct suspend_always {
constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(coroutine_handle<>) const noexcept { }
constexpr void await_resume() const noexcept { }
};
struct suspend_never {
constexpr bool await_ready() const noexcept { return true; }
constexpr void await_suspend(coroutine_handle<>) const noexcept { }
constexpr void await_resume() const noexcept { }
};本质区别只有一点:await_ready() 的返回值。
await_ready() | 效果 | |
|---|---|---|
suspend_always | false | 一定挂起 |
suspend_never | true | 一定不挂起,跳过 await_suspend |
当你用这两者搭配 initial_suspend/final_suspend:
initial_suspend | final_suspend | 典型用途 |
|---|---|---|
suspend_always | suspend_always | Generator(懒惰求值,手动管理生命周期) |
suspend_never | suspend_always | Task(立即执行异步任务,等待完成) |
suspend_never | suspend_never | Fire-and-forget(不关心结果的异步任务,危险!) |
10. 自定义 Awaiter 实战
场景:协程中等待一个异步操作
#include <coroutine>
#include <functional>
#include <iostream>
// 模拟一个异步 IO 完成通知
struct AsyncIO {
std::function<void()> callback; // 操作完成时调用
int result;
void complete(int val) {
result = val;
callback(); // 通知等待者
}
};
// 等待 AsyncIO 完成的 Awaiter
struct IOAwaiter {
AsyncIO& io;
// IO 操作还没完成,需要挂起
bool await_ready() { return false; }
// 挂起时:保存 handle,注册回调
void await_suspend(std::coroutine_handle<> h) {
io.callback = [h]() mutable {
h.resume(); // IO 完成时恢复协程
};
}
// 恢复时:返回 IO 结果
int await_resume() {
return io.result;
}
};
// 让 AsyncIO 直接支持 co_await
IOAwaiter operator co_await(AsyncIO& io) {
return IOAwaiter{io};
}
// 协程任务类型
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
// 使用自定义 Awaiter 的协程
Task do_work(AsyncIO& io) {
std::cout << "开始等待 IO...\n";
int result = co_await io; // 挂起,直到 io.complete() 被调用
std::cout << "IO 完成,结果:" << result << "\n";
}
int main() {
AsyncIO io;
do_work(io); // 协程启动,打印"开始等待 IO...",然后挂起
std::cout << "主线程继续执行...\n";
io.complete(42); // 模拟 IO 完成,这里会 resume 协程
// 协程打印"IO 完成,结果:42"
return 0;
}11. 协程 vs 线程 vs 回调
| 特性 | 回调(Callback) | 线程(Thread) | 协程(Coroutine) |
|---|---|---|---|
| 并发模型 | 事件驱动 | 抢占式多任务 | 协作式多任务 |
| 代码可读性 | 差(回调地狱) | 良(顺序代码) | 优(顺序代码) |
| 上下文切换开销 | 极低 | 高(内核级) | 极低(用户态) |
| 调度控制 | 事件循环决定 | 操作系统决定 | 程序员/框架决定 |
| 内存占用 | 低 | 高(~1MB/线程) | 低(按需分配帧) |
| 数据共享 | 复杂(闭包) | 需要锁 | 天然顺序访问 |
| 调试难度 | 高 | 中 | 低 |
12. 常见陷阱与注意事项
陷阱 1:在 final_suspend 返回 suspend_never 后访问句柄
// 错误!
struct BadGen {
struct promise_type {
std::suspend_never final_suspend() noexcept { return {}; }
// ...
};
std::coroutine_handle<promise_type> handle;
~BadGen() {
// 此时帧可能已经被自动销毁!
if (!handle.done()) handle.destroy(); // 如果已销毁,这是 UB!
}
};解决方案:final_suspend 始终返回 suspend_always,让 RAII 对象负责销毁。
陷阱 2:协程句柄的拷贝语义
coroutine_handle 是浅拷贝(只复制指针):
auto h1 = gen.handle;
auto h2 = h1; // h2 和 h1 指向同一个协程帧!
h1.destroy(); // 销毁帧
h2.resume(); // UB!h2 是悬空指针解决方案:协程对象(Generator)应该禁止拷贝,只允许移动:
struct Generator {
Generator(const Generator&) = delete;
Generator& operator=(const Generator&) = delete;
Generator(Generator&& other) : handle(other.handle) { other.handle = nullptr; }
// ...
};陷阱 3:在协程销毁后访问 promise 里的数据
Generator gen = counter(1, 3);
auto& p = gen.handle.promise(); // 获取 promise 引用
gen = Generator{}; // 旧 gen 析构,协程帧销毁
// p 现在是悬空引用!
p.current_value; // UB!陷阱 4:忘记检查 handle.done()
Generator gen = make_gen();
gen.handle.resume(); // 如果协程已经结束,这是 UB!
// 正确做法
if (!gen.handle.done()) {
gen.handle.resume();
}陷阱 5:在协程内抛出异常
struct promise_type {
void unhandled_exception() {
// 选项1:终止程序
std::terminate();
// 选项2:存储异常,在取值时重新抛出
exception_ = std::current_exception();
}
std::exception_ptr exception_;
};
// 取值时重新抛出
int value() {
if (handle.promise().exception_)
std::rethrow_exception(handle.promise().exception_);
return handle.promise().current_value;
}13. 进阶:异步任务协程
生成器(Generator)是协程的入门用例。真正"震撼"的是把协程用于异步任务。
13.1 Task 的设计目标
Task<T> 需要解决两个完全独立的问题:
| 问题 | 负责者 | 触发时机 |
|---|---|---|
| 这个协程自己跑完了,把控制权交给谁? | FinalSuspendAwaiter(在 promise_type 里) | 协程体执行完毕时,编译器自动触发 |
| 外层协程想等我完成,外层协程该怎么挂起? | CoAwaitAwaiter(在 Task 本体里) | 外层协程执行 co_await someTask 时触发 |
理解这一点是理解整个 Task 的钥匙。
13.2 continuation:连接两个协程的纽带
promise_type 里有一个关键字段:
std::coroutine_handle<> continuation; // "等我完成的那个外层协程"它是整个链路的核心数据:
greet 执行 co_await compute_task
│
└─► CoAwaitAwaiter::await_suspend(greet_handle)
写入:compute_task.promise.continuation = greet_handle
← "greet 在等我完成,记下来"
[compute 跑完]
│
└─► FinalSuspendAwaiter::await_suspend(compute_handle)
读取:continuation = greet_handle
return greet_handle
← "之前记下来的人,现在恢复它"13.3 FinalSuspendAwaiter 详解
// 位于 promise_type 内部
// 触发时机:这个协程的协程体执行完毕,编译器自动执行:
// co_await promise.final_suspend()
// final_suspend() 返回 FinalSuspendAwaiter,三段式协议随即触发
struct FinalSuspendAwaiter {
// 永远 false:必须走 await_suspend,因为需要在这里做"交还控制权"的操作
bool await_ready() noexcept { return false; }
// h:刚刚跑完的"我自己"的句柄
std::coroutine_handle<> await_suspend(
std::coroutine_handle<promise_type> h) noexcept
{
auto cont = h.promise().continuation;
if (cont) {
// 有人在等我 → 对称转移,直接跳到等待者(不回到 main)
return cont;
} else {
// 没人等我(最外层协程)→ noop,控制权回到 main
return std::noop_coroutine();
}
}
void await_resume() noexcept {}
};为什么要对称转移,而不是直接 cont.resume()?
朴素方案(cont.resume()): 对称转移(return cont):
main → resume(greet) main → resume(greet)
greet → resume(compute) greet 挂起时对称转移到 compute
compute 完成 compute 完成
compute.FinalSuspendAwaiter compute.FinalSuspendAwaiter
cont.resume(greet) ← 新帧! return greet_handle ← 尾调用,无新帧
greet 继续执行 greet 继续执行
resume 返回 resume 返回到 main
compute 结束 compute 结束
greet 结束
main
调用栈深度随链条增长 调用栈始终 O(1)13.4 CoAwaitAwaiter 详解
// 位于 Task 本体内(不在 promise_type 里)
// 触发时机:外层协程执行 co_await someTask 时
// 编译器先调用 someTask.operator co_await() → 得到 CoAwaitAwaiter
// 然后触发三段式协议
struct CoAwaitAwaiter {
std::coroutine_handle<promise_type> handle; // 被等待的 Task 的句柄
// 被等待的 Task 是否已经跑完了?
// 热启动(suspend_never):Task 调用后立即同步执行完,可能到这里已经 done=true
// 冷启动(suspend_always):Task 调用后立刻挂起,到这里 done=false
bool await_ready() noexcept { return handle.done(); }
// 只有 await_ready = false 时才进入
// caller:正在执行 co_await 的那个外层协程(greet)的句柄
std::coroutine_handle<> await_suspend(
std::coroutine_handle<> caller) noexcept
{
// 第1步:登记"等待者",Task 完成后 FinalSuspendAwaiter 会来取
handle.promise().continuation = caller;
// 第2步:对称转移,直接跳进 Task 开始/继续执行
// 注意:return handle 不是"把 handle 返回给调用方",
// 而是告诉编译器"请切换到 handle 这个协程执行"
return handle;
}
// 恢复后:取出 Task 的执行结果,这就是 co_await 表达式的返回值
T await_resume() {
if (handle.promise().exception)
std::rethrow_exception(handle.promise().exception);
return std::move(handle.promise().result);
}
};
// 让 Task 对象本身支持 co_await 语法
CoAwaitAwaiter operator co_await() noexcept {
return CoAwaitAwaiter{handle};
}13.5 两条执行路径
CoAwaitAwaiter::await_ready() 的返回值决定走哪条路:
路径 A:热启动,Task 已完成(await_ready = true)
greet 调用 compute()(热启动:立即执行)
compute 同步跑完,co_return 42
FinalSuspendAwaiter: continuation=空 → noop_coroutine → compute done=true
greet 执行 co_await compute_task
CoAwaitAwaiter::await_ready() → true(compute 已完成!)
跳过 await_suspend,直接 await_resume
取出结果 42,greet 继续执行
外层协程不挂起,如同调用普通函数路径 B:冷启动,Task 未完成(await_ready = false)
greet 调用 compute_lazy()(冷启动:立即挂起)
compute_lazy done=false,没有执行任何协程体
greet 执行 co_await compute_lazy_task
CoAwaitAwaiter::await_ready() → false(未完成)
CoAwaitAwaiter::await_suspend(greet_handle)
continuation = greet_handle ← 记住等待者
return compute_lazy_handle ← 对称转移①:跳进 compute_lazy
compute_lazy 协程体执行:co_return 42
compute_lazy FinalSuspendAwaiter::await_suspend
continuation = greet_handle(找到了!)
return greet_handle ← 对称转移②:跳回 greet
greet CoAwaitAwaiter::await_resume() ← 取出 42,greet 继续执行13.6 完整代码(含热启动和冷启动演示)
#include <coroutine>
#include <exception>
#include <utility>
#include <string>
#include <iostream>
// ── 热启动 Task(suspend_never)────────────────────────────────
template<typename T>
struct Task {
struct promise_type {
T result;
std::exception_ptr exception;
std::coroutine_handle<> continuation; // 等我完成的外层协程
Task<T> get_return_object() {
return Task<T>{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() noexcept { return {}; } // 热启动
struct FinalSuspendAwaiter {
bool await_ready() noexcept { return false; }
std::coroutine_handle<> await_suspend(
std::coroutine_handle<promise_type> h) noexcept {
auto cont = h.promise().continuation;
return cont ? cont : std::noop_coroutine(); // 对称转移回等待者
}
void await_resume() noexcept {}
};
FinalSuspendAwaiter final_suspend() noexcept { return {}; }
void return_value(T val) { result = std::move(val); }
void unhandled_exception() { exception = std::current_exception(); }
};
struct CoAwaitAwaiter {
std::coroutine_handle<promise_type> handle;
bool await_ready() noexcept { return handle.done(); } // 已完成则跳过挂起
std::coroutine_handle<> await_suspend(std::coroutine_handle<> caller) noexcept {
handle.promise().continuation = caller; // 登记等待者
return handle; // 对称转移进入 Task
}
T await_resume() {
if (handle.promise().exception)
std::rethrow_exception(handle.promise().exception);
return std::move(handle.promise().result);
}
};
CoAwaitAwaiter operator co_await() noexcept { return CoAwaitAwaiter{handle}; }
std::coroutine_handle<promise_type> handle;
explicit Task(std::coroutine_handle<promise_type> h) : handle(h) {}
~Task() { if (handle) handle.destroy(); }
Task(const Task&) = delete;
Task(Task&& o) : handle(std::exchange(o.handle, {})) {}
};
// ── 冷启动 LazyTask(suspend_always)──────────────────────────
template<typename T>
struct LazyTask {
struct promise_type {
T result;
std::exception_ptr exception;
std::coroutine_handle<> continuation;
LazyTask<T> get_return_object() {
return LazyTask<T>{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() noexcept { return {}; } // 冷启动,立即挂起
struct FinalSuspendAwaiter {
bool await_ready() noexcept { return false; }
std::coroutine_handle<> await_suspend(
std::coroutine_handle<promise_type> h) noexcept {
auto cont = h.promise().continuation;
return cont ? cont : std::noop_coroutine();
}
void await_resume() noexcept {}
};
FinalSuspendAwaiter final_suspend() noexcept { return {}; }
void return_value(T val) { result = std::move(val); }
void unhandled_exception() { exception = std::current_exception(); }
};
struct LazyCoAwaitAwaiter {
std::coroutine_handle<promise_type> handle;
bool await_ready() noexcept { return false; } // 冷启动必定未完成
std::coroutine_handle<> await_suspend(std::coroutine_handle<> caller) noexcept {
handle.promise().continuation = caller; // 登记等待者
return handle; // 对称转移启动 LazyTask
}
T await_resume() {
if (handle.promise().exception)
std::rethrow_exception(handle.promise().exception);
return std::move(handle.promise().result);
}
};
LazyCoAwaitAwaiter operator co_await() noexcept { return LazyCoAwaitAwaiter{handle}; }
std::coroutine_handle<promise_type> handle;
explicit LazyTask(std::coroutine_handle<promise_type> h) : handle(h) {}
~LazyTask() { if (handle) handle.destroy(); }
LazyTask(const LazyTask&) = delete;
LazyTask(LazyTask&& o) : handle(std::exchange(o.handle, {})) {}
};
// ── 演示使用 ──────────────────────────────────────────────────
// 热启动:co_await 时 compute 已完成,await_ready=true,不挂起
Task<int> compute() { co_return 42; }
Task<std::string> greet() {
int val = co_await compute(); // CoAwaitAwaiter::await_ready=true,直接取结果
co_return "The answer is " + std::to_string(val);
}
// 冷启动:co_await 时 compute_lazy 挂起,触发完整对称转移链
LazyTask<int> compute_lazy() { co_return 42; }
LazyTask<std::string> greet_lazy() {
int val = co_await compute_lazy(); // LazyCoAwaitAwaiter::await_ready=false,对称转移
co_return "The answer is " + std::to_string(val);
}
int main() {
// 热启动:greet 和 compute 同步跑完,result 直接可读
auto task = greet();
std::cout << task.handle.promise().result << "\n"; // The answer is 42
// 冷启动:greet_lazy 挂在 initial_suspend,需要手动 resume 触发整条链
auto lazy = greet_lazy();
lazy.handle.resume(); // 一次 resume 触发两次对称转移,直到 greet_lazy 完成
std::cout << lazy.handle.promise().result << "\n"; // The answer is 42
}13.7 实际运行输出分析
运行上面代码(加上打印语句后),输出如下:
═══ 演示一:热启动(Task,suspend_never)═══
[Task] initial_suspend → suspend_never,立即执行
[greet] 开始执行,准备 co_await compute()
[Task] initial_suspend → suspend_never,立即执行
[compute] 开始执行
[Task] return_value() 保存结果
[FinalSuspendAwaiter] 无等待者,返回 noop_coroutine ← compute 自己跑完,没人等
[CoAwaitAwaiter] await_ready = true(已完成,跳过挂起)← greet co_await 时 compute 已完成
[CoAwaitAwaiter] await_resume:取出结果
[greet] compute 完成,val = 42
[Task] return_value() 保存结果
[FinalSuspendAwaiter] 无等待者,返回 noop_coroutine ← greet 自己跑完,main 没等它
结果:The answer is 42
═══ 演示二:冷启动(LazyTask,suspend_always)═══
[LazyTask] initial_suspend → suspend_always ← greet_lazy 立即挂起
greet_lazy 已挂起,done = 0
手动 resume,触发对称转移链...
[greet_lazy] 被 resume 启动
[LazyTask] initial_suspend → suspend_always ← compute_lazy 立即挂起
[LazyCoAwaitAwaiter] await_ready = false(冷启动任务肯定没完成)
[LazyCoAwaitAwaiter] await_suspend:对称转移①→ 启动 compute_lazy
[compute_lazy] 被对称转移启动,开始执行
[LazyTask] return_value() 保存结果
[LazyTask::FinalSuspendAwaiter] 对称转移②→ 恢复外层 greet_lazy ← 对称转移回去
[LazyCoAwaitAwaiter] await_resume:取出结果
[greet_lazy] compute_lazy 完成,val = 42
[LazyTask] return_value() 保存结果
[LazyTask::FinalSuspendAwaiter] 无等待者,返回 noop_coroutine
结果:The answer is 42可以清楚地看到两次对称转移的发生点。
14. 全景脑图:所有概念的关系图
C++20 协程体系
│
├── 协程函数(Coroutine Function)
│ ├── 包含 co_yield / co_await / co_return 关键字
│ ├── 返回值类型是"协程返回类型"(用户自定义)
│ └── 被编译器变换为状态机 + 协程帧
│
├── 协程帧(Coroutine Frame)—— 堆上分配
│ ├── resume 函数指针
│ ├── destroy 函数指针
│ ├── suspend_index(当前挂起点)
│ ├── promise_type 实例
│ ├── 函数参数(拷贝)
│ ├── 局部变量
│ └── 各挂起点的 Awaiter 实例
│
├── 协程返回类型(Coroutine Return Object,用户命名)
│ ├── 例:Generator, Task, Fiber, Lazy
│ ├── 必须包含 promise_type
│ ├── 通常持有 coroutine_handle
│ └── 向外暴露用户友好接口(next/value/await/...)
│
├── promise_type(固定名字,协程控制协议)
│ ├── get_return_object() → 创建协程返回对象
│ ├── initial_suspend() → 决定启动时是否挂起
│ ├── final_suspend() → 决定结束时是否挂起
│ ├── yield_value(v) → 处理 co_yield
│ ├── return_value(v) → 处理 co_return v
│ ├── return_void() → 处理 co_return;
│ ├── unhandled_exception() → 处理未捕获异常
│ └── await_transform(v) → 拦截 co_await(可选)
│
├── coroutine_handle(协程句柄,指向协程帧的指针)
│ ├── resume() → 恢复协程
│ ├── destroy() → 销毁协程帧
│ ├── done() → 是否到达 final_suspend
│ ├── promise() → 获取 promise 引用
│ ├── from_promise(p) → 从 promise 构造句柄
│ └── from_address(p) → 从原始指针构造
│
├── Awaiter(等待体,控制挂起逻辑)
│ ├── await_ready() → 是否已就绪(跳过挂起)
│ ├── await_suspend(h) → 挂起时执行(保存 handle,注册回调)
│ └── await_resume() → 恢复时执行(返回 co_await 的值)
│
├── 标准 Awaiter
│ ├── suspend_always → await_ready=false(一定挂起)
│ └── suspend_never → await_ready=true(一定不挂起)
│
└── 关键字
├── co_yield expr → co_await promise.yield_value(expr)
├── co_await expr → 展开为 awaiter 的三段式调用
└── co_return [expr] → promise.return_[void|value]() + goto final15. FinalSuspendAwaiter vs CoAwaitAwaiter:如何区分?
初学者最容易混淆的一点:FinalSuspendAwaiter、CoAwaitAwaiter、suspend_always 都有 await_ready/await_suspend/await_resume 三个函数,它们到底分别在什么时候触发?
15.1 一句话区分
| 结构体 | 所处位置 | 触发者 | 触发时机 |
|---|---|---|---|
FinalSuspendAwaiter | promise_type 内部 | 这个协程自己 | 这个协程的协程体跑完时 |
CoAwaitAwaiter | Task 本体内部 | 外层协程 | 外层协程 co_await 这个 Task 时 |
suspend_always | 任何位置 | 谁调用就谁触发 | initial_suspend/final_suspend/yield_value 返回它时 |
15.2 从代码位置理解
Task<int> compute() { // ← 这是一个协程
co_return 42;
} // ← 协程体结束,编译器自动执行:
// co_await promise.final_suspend()
// ↑
// final_suspend() 返回 FinalSuspendAwaiter
// ★ FinalSuspendAwaiter 的三段式在此触发 ★
// 触发者:compute 协程自己
Task<std::string> greet() {
int val = co_await compute();
// ↑
// 编译器调用 compute_task.operator co_await() → 得到 CoAwaitAwaiter
// ★ CoAwaitAwaiter 的三段式在此触发 ★
// 触发者:greet 协程(外层,在等 compute)
}15.3 方向相反的两个 Awaiter
视角一(compute 自己): 视角二(greet 等 compute):
「我跑完了,该交控制权了」 「我要等你,我先挂起」
↓ ↓
FinalSuspendAwaiter CoAwaitAwaiter
位于 promise_type 内部 位于 Task 本体内部
编译器在协程体末尾自动触发 外层协程 co_await 时触发
await_suspend 返回 continuation await_suspend 存入 continuation
← 把控制权「交出去」 ← 把「自己的 handle」存进去两者合作完成一次完整的协程链切换:CoAwaitAwaiter 存入 continuation,FinalSuspendAwaiter 取出 continuation。
15.4 时序全景图(冷启动版)
main
│
├─► greet_lazy() 被调用
│ └─ initial_suspend → 挂起(冷启动)
│
├─► lazy_task.handle.resume() ← main 唯一一次主动 resume
│ │
│ └─► greet_lazy 协程体开始执行
│ │
│ ├─► compute_lazy() 被调用 → initial_suspend → 挂起(冷启动)
│ │
│ └─► co_await compute_lazy_task
│ │
│ │ ★ CoAwaitAwaiter 触发(greet 的视角:我要等 compute)★
│ ├─ await_ready() → false
│ ├─ await_suspend(greet_handle)
│ │ continuation = greet_handle ← 写入等待者
│ │ return compute_handle ─────────────────────────┐
│ │ 对称转移① │
│ │ ◄─────────────────────────────────┘
│ │ compute_lazy 协程体执行
│ │ co_return 42 → 结果保存
│ │
│ │ ★ FinalSuspendAwaiter 触发(compute 的视角:我跑完了)★
│ │ await_ready() → false
│ │ await_suspend(compute_handle)
│ │ return continuation(greet_handle) ──┐
│ │ 对称转移② │
│ │ ◄───────────────────────────────────────────────┘
│ └─ await_resume() → 取出 42,greet 继续执行
│
└─► resume() 返回 main,main 读取结果15.5 记忆口诀
FinalSuspendAwaiter: 「我(这个协程)跑完了,把控制权 交出去」
→ 在 promise_type 里,协程体结束时自动触发
→ await_suspend 读取 continuation,对称转移回去
CoAwaitAwaiter: 「你(外层协程)要等我,你把自己的 handle 存进来」
→ 在 Task 本体里,被 co_await 时触发
→ await_suspend 写入 continuation,然后对称转移进来
两者都有 await_ready/await_suspend/await_resume,
但触发方向恰好相反:一个「交出去」,一个「存进来」。附录:快速参考卡
promise_type 检查清单
struct MyPromise {
// ✅ 必须:创建协程对象
MyCoroutine get_return_object();
// ✅ 必须:决定启动时是否挂起
std::suspend_always /* or suspend_never */ initial_suspend();
// ✅ 必须:决定结束时是否挂起(必须 noexcept)
std::suspend_always /* or suspend_never */ final_suspend() noexcept;
// ✅ 必须:处理未捕获异常
void unhandled_exception();
// ✅ 二选一:有返回值 or 无返回值
void return_void(); // co_return; 或函数体结束
void return_value(SomeType value); // co_return expr;
// ✅ 使用 co_yield 时必须:
SomeAwaiter yield_value(YieldType value);
// ⬜ 可选:拦截所有 co_await
SomeAwaiter await_transform(SomeType value);
// ⬜ 可选:自定义内存分配
static void* operator new(std::size_t sz);
static void operator delete(void* ptr, std::size_t sz);
};Awaiter 检查清单
struct MyAwaiter {
// 1. 是否已就绪?true=不挂起,false=挂起
bool await_ready();
// 2. 挂起时执行(三种返回类型之一)
void await_suspend(std::coroutine_handle<> h); // 无条件挂起
bool await_suspend(std::coroutine_handle<> h); // 条件挂起
std::coroutine_handle<> await_suspend(std::coroutine_handle<> h); // 对称转移
// 3. 恢复后执行(co_await 的返回值)
ReturnType await_resume();
};典型 Generator 模板
template<typename T>
struct Generator {
struct promise_type {
T current_value;
Generator get_return_object() {
return Generator{
std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(T value) {
current_value = std::move(value);
return {};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }
Generator(const Generator&) = delete;
Generator(Generator&& o) : handle(std::exchange(o.handle, {})) {}
bool next() {
if (handle && !handle.done()) {
handle.resume();
return !handle.done();
}
return false;
}
T& value() { return handle.promise().current_value; }
std::coroutine_handle<promise_type> handle;
};