积累沉淀

待山花烂漫,化茧成蝶

C++20 Coroutine-协程

前言:协程(Coroutine)是一种计算机程序组件,它与子例程(即通常所说的函数或过程)类似,但其执行方式更为灵活。不同于传统的线程和进程,协程允许在特定的地方暂停执行,并能在之后从暂停的地方恢复执行。这种特性使得协程在处理异步编程、并发操作以及I/O密集型任务时特别有用。

C++20 协程(Coroutines)学习指南

基于 cppreference.com、David Mazières(Stanford)教程、Simon Tatham 教程及知乎专栏整理
从概念到实践,逐层深入理解 C++20 无栈协程


目录

  1. 协程初探 —— 概念与动机
  2. 协程的核心三要素
  3. 第一个协程 —— 从零开始
  4. 协程的完整生命周期与控制流
  5. 深入 co_await —— 等待协议
  6. 承诺类型(Promise Type)完全解读
  7. 协程句柄(coroutine_handle)详解
  8. co_yield 与生成器模式
  9. co_return 与协程完成
  10. 对称转移(Symmetric Transfer)与协程链
  11. 实际应用模式
  12. 常见陷阱与最佳实践

第一章:协程初探 —— 概念与动机

1.1 什么是协程?

协程(Coroutine) 是一个可以暂停执行(suspend) 并在之后恢复执行(resume) 的函数。

普通函数一旦被调用,就从入口执行到出口,中间不可中断:

1
2
3
4
int normal_function() {
int x = compute(); // 一直执行完
return x; // 返回,结束
}

而协程可以在中间某个点暂停,将控制权交还给调用者,然后在之后的某个时刻从暂停点继续执行:

1
2
3
4
5
task my_coroutine() {
int x = co_await async_compute(); // 暂停!返回给调用者
// 稍后恢复,继续执行
co_return x; // 结束协程
}

一句话总结:协程 = 可以暂停和恢复的函数。

1.2 协程 vs 线程

维度 协程(Coroutine) 线程(Thread)
调度方式 协作式(程序自己让出控制权) 抢占式(操作系统强制切换)
切换成本 ≈ 函数调用(纳秒级) 系统调用(微秒级,约 1000×)
栈空间 无独立栈(C++20 无栈协程) 有独立栈(通常 1-8 MB)
并发模型 单线程内多个协程交替执行 多核并行执行
创建数量 数十万甚至百万 几千到数万

核心区别:协程是你可以暂停的函数,线程是你可以暂停的进程。协程的切换发生在用户态,完全由程序控制;线程的切换由操作系统内核完成,不可预测。

1.3 回调地狱 —— 协程要解决的问题

在异步编程中,传统做法是使用回调函数。当异步操作嵌套时,代码会急剧恶化:

1
2
3
4
5
6
7
8
9
10
// 传统回调方式:回调地狱
async_resolve({host, port}, [](auto endpoint) {
async_connect(endpoint, [](auto error_code) {
async_handshake([](auto error_code) {
async_write(send_data_, [](auto error_code) {
async_read();
});
});
});
});

使用协程后,同样的逻辑可以写成顺序代码:

1
2
3
4
5
6
7
8
// C++20 协程方式:同步语法写异步代码
task<void> client_session() {
auto endpoint = co_await async_resolve({host, port});
auto error_code = co_await async_connect(endpoint);
error_code = co_await async_handshake();
error_code = co_await async_write(send_data_);
co_await async_read();
}

知乎文章指出,这正是 C++20 引入协程的核心动机——“以同步语法写异步代码”

1.4 栈式协程 vs 无栈协程

对比维度 有栈协程(Stackful) 无栈协程(Stackless)
栈空间 堆上预分配独立栈(如 64KB) 无独立栈,复用线程栈
栈溢出风险 有(需预分配足够空间)
挂起深度 可嵌套挂起(任意深度函数调用) 只能在协程体自身挂起
切换性能 需 swapcontext,较重 ≈ 函数调用,纳秒级
是否 C++20 标准
代表实现 Boost.Coroutine, libco C++20 标准协程

C++20 选择了微软主导提出的无栈协程方案。无栈协程的本质是编译器将其转换为一个状态机,所有的局部变量被保存到堆分配的协程帧中。切换时不需要切换栈,因此成本极低。

知乎文章中的关键洞察:“无栈协程是普通函数的泛化”——普通函数只能一次性执行完毕,而无栈协程可以暂停和恢复,是对函数调用概念的推广。

1.5 C++20 协程的定位:构建套件

Simon Tatham 在他的文章中犀利地指出:C++20 没有提供开箱即用的协程——它提供的是构建套件(construction kit)。这意味着你必须自己实现大量样板代码:

  • 定义承诺类型(promise_type)来决定协程的行为策略
  • 定义等待器类型(awaiter)来控制挂起/恢复的语义
  • 定义返回对象类型来决定调用者如何与协程交互

如 Mazières 所言,C++20 协程就像"一堆垃圾下面的一个小金块",它灵活但学习曲线陡峭

1.6 三个新关键字一览

关键字 用途 对应承诺方法
co_await expr 暂停执行,等待异步操作完成 await_transform
co_yield expr 向调用者返回一个值并暂停 yield_value(expr)
co_return expr 结束协程并返回值 return_value(expr)
co_return; 结束协程,无返回值 return_void()

函数体中只要包含以上任一关键字,该函数就是协程。

1.7 协程的限制

C++20 协程不能用于:

  • 使用变长实参(variadic arguments)的函数
  • 使用普通 return 语句的函数
  • 返回类型为 auto 或 Concept 的函数
  • consteval / constexpr 函数
  • 构造函数 / 析构函数
  • main 函数

第二章:协程的核心三要素

每个 C++20 协程由三个核心对象协作完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌──────────────────────────────────────────────────────┐
│ 协程帧 (Coroutine Frame) │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ 参数副本 │ │ promise 对象 │ │
│ │ (参数1, 参数2)│ │ (promise_type)│ │
│ ├─────────────┤ ├──────────────┤ │
│ │ 局部变量 │ │ 暂停点信息 │ │
│ │ (栈上变量的 │ │ (resume 地址) │ │
│ │ 堆上副本) │ │ │ │
│ └─────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────┘
▲ ▲
│ 从内部访问 │ 从外部操控
│ │
┌───────┴──────────┐ ┌─────────┴────────┐
│ promise 对象 │ │ coroutine_handle │
│ (内部接口) │ │ (外部句柄) │
│ • initial_suspend │ │ • resume() │
│ • return_void │ │ • destroy() │
│ • yield_value │ │ • done() │
│ • unhandled_exc │ │ • from_promise() │
└──────────────────┘ └──────────────────┘

2.1 协程帧(Coroutine State / Coroutine Frame)

  • 动态分配在堆上的存储区域(某些情况下编译器可优化为栈分配)
  • 包含:形参副本、promise 对象、局部变量、暂停点信息
  • 生命周期:从协程调用开始,到协程销毁结束
  • 通过 coroutine_handle 引用

2.2 承诺对象(Promise Object)

  • 类型为 promise_type(嵌套在返回类型中)
  • 协程内部的控制器,决定协程的行为策略
  • 负责:提交结果、处理异常、控制初始/最终挂起行为
  • 不是 std::promise——两者除了名字之外毫无关系

2.3 协程句柄(Coroutine Handle)

  • 类型为 std::coroutine_handle<Promise>,模板参数可省略
  • 协程外部的操控器,用于:
    • resume() / operator()() —— 恢复协程执行
    • destroy() —— 销毁协程帧
    • done() —— 判断协程是否已执行完毕
    • promise() —— 访问内部的 promise 对象
  • 非拥有句柄(类似指针),不负责生命周期

2.4 返回对象(Return Object)

  • 调用者看到的返回值类型
  • promise.get_return_object() 创建
  • 通常持有 coroutine_handle 以供调用者恢复协程
  • 返回对象析构时可以选择是否 destroy() 协程帧

第三章:第一个协程 —— 从零开始

本章基于 Mazières 教程和 Tatham 的最小示例,构建第一个完整的协程。

3.1 最简单的 promise_type

创建一个协程,需要三个要素:

  1. 返回类型:嵌套 promise_type 的类
  2. 承诺类型:实现所需的接口方法
  3. 外部代码:通过 coroutine_handle 控制协程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <coroutine>
#include <iostream>

// 最简单的返回类型
struct ReturnObject {
struct promise_type {
ReturnObject get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};

// 协程函数
ReturnObject counter() {
std::cout << "counter: 开始执行" << std::endl;
for (unsigned i = 0; i < 3; ++i) {
std::cout << "counter: " << i << std::endl;
co_await std::suspend_always{};
}
std::cout << "counter: 执行结束" << std::endl;
co_return;
}

int main() {
// 1. 调用协程
auto ret = counter();
// 2. 协程已自动执行完(因为 initial_suspend = suspend_never)
// 且无需手动销毁(因为 final_suspend = suspend_never)
std::cout << "main: 协程已自动执行完毕" << std::endl;
return 0;
}

输出

1
2
3
4
5
6
counter: 开始执行
counter: 0
counter: 1
counter: 2
counter: 执行结束
main: 协程已自动执行完毕

注意:使用 std::suspend_never 作为 initial_suspendfinal_suspend 的结果意味着协程既不延迟启动也不在结束时挂起——一切自动进行。

3.2 手动控制 —— 使用外部 Awaiter

要让调用者控制协程的暂停和恢复,需要自定义 awaiter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <coroutine>
#include <iostream>

struct ReturnObject {
struct promise_type {
ReturnObject get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};

struct Awaiter {
std::coroutine_handle<> *hp_;

// 是否已就绪?false → 需要挂起
bool await_ready() const noexcept { return false; }

// 挂起时执行:保存 handle 到外部
void await_suspend(std::coroutine_handle<> h) noexcept {
*hp_ = h;
}

// 恢复时返回值
void await_resume() const noexcept {}
};

ReturnObject counter(std::coroutine_handle<> &h) {
Awaiter awaiter{&h};
for (unsigned i = 0;; ++i) {
co_await awaiter; // → 保存 handle 到 h,然后挂起
std::cout << "counter: " << i << std::endl;
}
}

int main() {
std::coroutine_handle<> h;
counter(h); // 执行到第一个 co_await,挂起

for (int i = 0; i < 3; ++i) {
std::cout << "main: 恢复协程" << std::endl;
h.resume(); // 恢复执行
}
h.destroy(); // 手动销毁协程帧
std::cout << "main: 完成" << std::endl;
return 0;
}

输出

1
2
3
4
5
6
7
main: 恢复协程
counter: 0
main: 恢复协程
counter: 1
main: 恢复协程
counter: 2
main: 完成

执行流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
main() 调用 counter(h)
→ 分配协程帧
→ promise 构造
→ initial_suspend() → suspend_never,继续
→ 进入 for 循环
→ co_await awaiter
→ await_ready() == false,挂起
→ await_suspend(handle):保存 handle 到 &h
→ 返回给 main(此时 h 已有效)
→ main 调用 h.resume()
→ await_resume()
→ 继续循环
→ 打印 counter: 0
→ co_await awaiter(再次挂起)
→ main 继续...

3.3 编译要求

编译器 命令
GCC g++ -std=c++20 -fcoroutines file.cpp
Clang clang++ -std=c++20 -stdlib=libc++ file.cpp
MSVC cl /std:c++20 file.cpp(默认启用)

注意:不同编译器对协程的支持成熟度不同。GCC 和 MSVC 对 C++20 协程支持最好,Clang 需要 -fcoroutines-ts 标志并使用 <experimental/coroutine>


第四章:协程的完整生命周期与控制流

4.1 生命周期流程图

graph TD
    A[调用协程函数] --> B[分配协程帧
operator new] B --> C[复制参数到协程帧] C --> D[构造 promise 对象] D --> E[promise.get_return_object] E --> F[promise.initial_suspend] F --> G{是否立即挂起?} G -->|suspend_always| H[挂起,返回 return_object] G -->|suspend_never| I[继续执行协程体] H --> J[调用者稍后 resume] J --> I I --> K{遇到什么操作?} K -->|co_await expr| L[等待协议
见第5章流程图] K -->|co_yield expr| M[yield_value
保存值并可能挂起] K -->|co_return| N[return_void/return_value] K -->|异常| O[unhandled_exception] L --> I M --> I N --> P[逆序销毁局部变量] O --> P P --> Q[promise.final_suspend] Q --> R{是否挂起?} R -->|suspend_always| S[挂起,调用者需 destroy] R -->|suspend_never| T[自动销毁] S --> U[调用者调用 destroy] U --> V[~promise / ~参数副本 / 释放帧] T --> V

4.2 各阶段详解

阶段一:创建(Coroutine Creation)

当调用一个协程函数时,编译器生成的代码执行以下步骤:

  1. 分配协程帧:调用 operator new 在堆上分配协程帧(可被优化掉)
  2. 复制参数:将所有形参复制到协程帧中保存
  3. 构造 promise:在帧内构造 promise_type 对象
  4. get_return_object():调用 promise.get_return_object() 创建返回给调用者的对象
  5. initial_suspend():调用 promise.initial_suspend()co_await 其结果
    • 返回 suspend_always → 协程挂起,控制权返回给调用者(惰性启动
    • 返回 suspend_never → 协程立即执行协程体(立即启动

参数生命周期陷阱:如果协程形参是引用类型,且引用对象在协程挂起后销毁,则恢复时引用悬垂。如:

1
2
3
4
task foo(const T& ref) {   // 警告:按引用传递
co_await something; // 挂起点
ref.use(); // 危险!ref 可能已悬垂
}

最佳实践:协程参数按值传递。

阶段二:执行(Coroutine Body Execution)

协程体正常执行,遇到以下情况之一会触发挂起或结束:

  • co_await expr → 执行等待协议(详见第5章)
  • co_yield expr → 调用 promise.yield_value(expr)co_await 结果
  • co_return expr → 调用 promise.return_value(expr)
  • co_return; → 调用 promise.return_void()
  • 函数末尾自然结束 → 调用 promise.return_void()
  • 异常抛出 → 调用 promise.unhandled_exception() 处理异常

阶段三:挂起与恢复(Suspend & Resume)

当协程遇到 co_await 且等待器决定挂起时:

  1. 协程的当前执行状态(恢复点、寄存器)保存在协程帧中
  2. 控制权返回给"恢复者"(调用 resume() 的那个代码)
  3. 之后,任何代码(可在任意线程)调用 handle.resume() 恢复执行
  4. 协程从挂起点之后继续执行

阶段四:完成(Coroutine Completion)

当协程执行完(co_return 或自然结束):

  1. 调用 promise.return_void()promise.return_value(expr)
  2. 逆序销毁所有自动存储期局部变量
  3. 调用 promise.final_suspend()co_await 其结果
    • 返回 suspend_always → 协程挂起,须由调用者显式 destroy()
    • 返回 suspend_never → 协程帧自动销毁

重要final_suspend() 返回 suspend_always 才可安全地在协程结束后读取 promise 中的数据。如果返回 suspend_never,协程帧会立即销毁,访问 promise 是未定义行为。

阶段五:销毁(Coroutine Destruction)

  1. promise 对象析构
  2. 形参副本析构
  3. 释放协程帧内存(operator delete

第五章:深入 co_await —— 等待协议

5.1 co_await 的完整处理流程

co_await expr 是协程中最核心的机制。编译器将其展开为一系列步骤:

graph TD
    A["co_await expr"] --> B["initial/final_suspend
或 yield_value 上下文?"] B -->|否| C["调用 promise.await_transform(expr)"] B -->|是| D["expr 即为 awaitable"] C --> E{"awaitable 是否有
operator co_await() ?"} D --> E E -->|有| F["调用 operator co_await()
得到 awaiter"] E -->|无| G["awaitable 自身作为 awaiter"] F --> H["调用 await_ready()"] G --> H H --> I{"结果已就绪?"} I -->|true| J["不挂起
直接调用 await_resume()"] I -->|false| K["挂起协程
保存恢复点"] K --> L["调用 await_suspend(handle)"] L --> M{"返回类型?"} M -->|void| N["挂起,返回给调用者"] M -->|bool| O{"true→挂起?
false→立即恢复?"} O -->|false| P["不挂起
行为同 await_ready=true"] O -->|true| N M -->|coroutine_handle| Q["立即恢复指定协程
对称转移"] N --> R["外部代码调用 resume()"] R --> S["调用 await_resume()"] P --> S Q --> S J --> T["co_await 表达式结果
= await_resume() 返回值"] S --> T

5.2 两个转换步骤

第一步:expr → Awaitable

编译器将 co_await expr 中的 expr 转换为可等待体(awaitable)

  • 如果该协程的 promise 类型定义了 await_transform() 且不是 initial/final suspend 或 yield 的上下文:
    • awaitable = promise.await_transform(expr)
  • 否则:
    • awaitable = expr(直接使用表达式本身)

第二步:Awaitable → Awaiter

编译器将 awaitable 转换为等待器(awaiter)

  • 如果 awaitable 有 operator co_await() 成员(或自由函数):
    • awaiter = awaitable.operator co_await()
  • 否则:
    • awaiter = awaitable(awaitable 自身即为 awaiter)

5.3 等待器三成员函数

任何一个等待器(awaiter)必须实现以下三个函数:

await_ready()

1
bool await_ready() const noexcept;
  • 返回 true:表示结果已就绪,不需要挂起,直接继续执行
  • 返回 false:表示需要挂起协程,进入等待

这是高效短路机制——如果异步操作已完成(如数据已缓存),可以直接跳过挂起/恢复的开销。

await_suspend()

1
2
3
4
5
6
7
8
9
10
11
12
13
// 三种合法的返回类型:

// 形式1:void —— 挂起后无条件让出控制权
void await_suspend(std::coroutine_handle<> h) noexcept;

// 形式2:bool —— 条件性挂起
bool await_suspend(std::coroutine_handle<> h) noexcept;
// true → 挂起
// false → 不挂起(同 await_ready 返回 true)

// 形式3:coroutine_handle —— 对称转移
std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept;
// 返回另一个协程的 handle → 立即恢复那个协程(不做嵌套 resume)

接收的参数是当前协程的 coroutine_handle<>(类型擦除的句柄)。

重要安全提示await_suspend 的参数 h 代表当前协程,当 await_suspend 被调用时,协程已经完全挂起。因此 await_suspend 可以将 h 发布到另一线程,那个线程可以调用 h.resume()但这也意味着当前协程可能在与 await_suspend 并发执行,所以 await_suspend 不应该在发布 h 后假设 *this(awaiter 自身)仍然存活。

1
2
3
4
5
6
// 危险示例:发布 handle 后访问 this
void await_suspend(std::coroutine_handle<> h) {
thread_pool.submit([h] { h.resume(); });
// ⚠️ this 可能在上一行被销毁了!不要访问成员变量
// this->flag = false; // 未定义行为!
}

await_resume()

1
2
// 可以返回任意类型(包括 void)
T await_resume() const noexcept;
  • 在协程恢复后调用
  • 返回值即为 co_await 表达式的值
  • 如果抛出异常,异常会传播到 co_await 表达式中

5.4 标准库工具

std::suspend_always —— 总是挂起

1
2
3
4
5
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 {}
};

std::suspend_never —— 从不挂起

1
2
3
4
5
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 {}
};
用途 常用类型
initial_suspend() 惰性启动 suspend_always
initial_suspend() 立即启动 suspend_never
final_suspend() 安全访问 promise suspend_always
final_suspend() 自动销毁 suspend_never(谨慎使用)
co_yield 挂起让调用者读取 suspend_always
co_await 等待异步操作 自定义 awaiter

5.5 完整示例:co_await 三种返回类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <coroutine>
#include <iostream>
#include <optional>

// 示例 1:await_suspend 返回 void
struct VoidAwaiter {
bool await_ready() noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) noexcept {
std::cout << "挂起协程,handle = " << h.address() << std::endl;
}
void await_resume() noexcept {}
};

// 示例 2:await_suspend 返回 bool
struct BoolAwaiter {
int step_;
bool await_ready() noexcept { return false; }
bool await_suspend(std::coroutine_handle<> h) noexcept {
if (step_ == 0) { step_ = 1; return true; } // 挂起
return false; // 不挂起,立即恢复
}
void await_resume() noexcept {}
};

// 示例 3:await_suspend 返回 coroutine_handle(对称转移)
struct TransferAwaiter {
std::coroutine_handle<> target_;
bool await_ready() noexcept { return false; }
std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept {
return target_; // 立即恢复 target_,不返回给 h 的调用者
}
void await_resume() noexcept {}
};

第六章:承诺类型(Promise Type)完全解读

6.1 Promise Type 的查找机制

编译器使用 std::coroutine_traits<R, Args...> 来确定协程的 promise 类型:

  • 默认实现:在返回类型 R 中查找 R::promise_type
  • 特化:可特化 std::coroutine_traits 来使用外部 promise 类型
1
2
3
4
5
6
7
8
9
10
// 返回类型结构:
struct my_return_type {
struct promise_type {
// ... 协程行为定义
};
};

// 编译器推导:
// coroutine_traits<my_return_type, int>::promise_type
// → my_return_type::promise_type

对于成员函数,coroutine_traits 会自动包含隐含的 this 指针类型:

1
2
3
4
5
6
7
8
class MyClass {
my_return_type foo(int x) const { // 成员函数
co_return;
}
};

// 编译器推导:
// coroutine_traits<my_return_type, const MyClass&, int>::promise_type

如果不想在返回类型中嵌套 promise_type,可以特化 coroutine_traits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 外部 promise 类型定义
struct external_promise {
my_return_type get_return_object();
std::suspend_never initial_suspend();
std::suspend_never final_suspend() noexcept;
void return_void();
void unhandled_exception();
};

// 特化 coroutine_traits
template<typename... Args>
struct std::coroutine_traits<my_return_type, Args...> {
using promise_type = external_promise;
};

6.2 承诺接口详解

一个完整的 promise_type 需要实现以下接口:

get_return_object()

1
ReturnType get_return_object();
  • 创建返回给调用者的对象
  • 通常在内部使用 coroutine_handle<promise_type>::from_promise(*this) 获取句柄并存入返回对象
1
2
3
4
5
6
7
8
struct promise_type {
ReturnType get_return_object() {
return ReturnType{
.h_ = std::coroutine_handle<promise_type>::from_promise(*this)
};
}
// ...
};

initial_suspend()

1
2
3
// 返回一个 awaitable 对象
std::suspend_always initial_suspend() { return {}; } // 惰性启动
std::suspend_never initial_suspend() { return {}; } // 立即启动
  • 控制协程是否在创建后立即挂起
  • 生成器通常返回 suspend_always(等待调用者首次 resume
  • 立即执行的任务通常返回 suspend_never

final_suspend()

1
2
3
// 必须标记 noexcept!否则未定义行为
std::suspend_always final_suspend() noexcept { return {}; } // 推荐
std::suspend_never final_suspend() noexcept { return {}; } // 谨慎
  • 协程执行完毕(co_return 或异常处理后)调用
  • 必须为 noexcept,否则行为未定义
  • 返回 suspend_always:协程帧保持有效,调用者可在之后 destroy()
  • 返回 suspend_never:协程帧立即自动销毁

如果需要从 promise 中读取数据(如生成器的 value_),final_suspend() 必须返回 suspend_always,否则协程帧在读取前就被销毁了。

return_void() vs return_value()

1
2
3
4
5
6
7
// 二选一,不能同时存在(编译错误)

// 用于 co_return; 或自然结束
void return_void() {}

// 用于 co_return expr;
void return_value(T value) {}
  • co_return;promise.return_void()
  • co_return exprpromise.return_value(expr)
  • 协程体末尾自然结束 → promise.return_void()
  • 如果定义了 return_value() 但协程中写了 co_return; 则编译错误,反之亦然

yield_value()

1
2
3
4
5
// 等价于 co_await promise.yield_value(expr)
std::suspend_always yield_value(T value) {
this->value_ = std::move(value); // 保存值
return {}; // 挂起,让调用者读取
}
  • co_yield expr 编译器展开为 co_await promise.yield_value(expr)
  • 返回的 awaitable 控制是否在 yield 后挂起
  • 通常用于生成器模式

unhandled_exception()

1
2
3
4
void unhandled_exception() {
// 保存异常,后续重新抛出
exception_ = std::current_exception();
}
  • 当协程体内抛出未被捕获的异常时调用
  • 调用后,编译器会自动调用 final_suspend()
  • 通常保存异常指针,在之后读取结果时重新抛出

6.3 完整的 promise 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
template<typename T>
struct Generator {
struct promise_type {
T value_; // 存储生成的值
std::exception_ptr exception_; // 存储异常

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 {}; } // 安全

void return_void() {}

void unhandled_exception() {
exception_ = std::current_exception();
}

std::suspend_always yield_value(T value) {
value_ = std::move(value);
return {};
}
};

// ...(见第8章完整代码)
};

第七章:协程句柄(coroutine_handle)详解

7.1 什么是 coroutine_handle?

std::coroutine_handle<Promise> 是一个非拥有句柄,类似于一个指针,指向协程帧。它是协程外部世界的操控接口。

  • 定义在 <coroutine> 头文件中
  • LiteralType(可在编译期构造)
  • 通常是 TriviallyCopyable(可简单拷贝)
  • 析构函数不会销毁协程帧——需要显式调用 destroy()

7.2 基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <coroutine>

// 假设有一个 promise 类型
struct promise_type { /* ... */ };
using handle_t = std::coroutine_handle<promise_type>;

int main() {
handle_t handle;

// --- 创建句柄 ---
handle = handle_t::from_promise(promise); // 从 promise 引用创建
// handle_t::from_address(addr); // 从 void* 地址恢复

// --- 控制协程 ---
handle.resume(); // 恢复协程执行
handle(); // 等价于 resume()(operator() 重载)
handle.destroy(); // 销毁协程帧

// --- 查询状态 ---
bool finished = handle.done(); // 协程是否已执行完(含 final_suspend 挂起后)
void* addr = handle.address(); // 获取协程帧的地址(可作唯一标识)

// --- 访问 promise ---
promise_type& p = handle.promise();
auto value = p.value_;
}

7.3 类型体系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 类型擦除版本:coroutine_handle<void>
// 不携带 promise 类型信息
std::coroutine_handle<> void_handle;

// 类型化版本:coroutine_handle<Promise>
// 可从 handle.promise() 获取特定类型的 promise 引用
std::coroutine_handle<promise_type> typed_handle;

// 转换:类型化 → 类型擦除(隐式转换)
typed_handle = /* ... */;
void_handle = typed_handle; // 隐式转换 OK

// 转换:类型擦除 → 类型化(需要 from_address)
typed_handle = std::coroutine_handle<promise_type>::from_address(
void_handle.address()
);

7.4 noop_coroutine

std::noop_coroutine() 是一个特殊的协程句柄,表示一个"无操作"协程:

1
2
3
4
5
6
7
8
#include <coroutine>

std::noop_coroutine_handle noop = std::noop_coroutine();

// 特点:
noop.resume(); // 安全,但什么都不做
noop.destroy(); // 安全,但什么都不做
noop.done(); // 永远返回 true
  • 类型为 std::noop_coroutine_handle(是 coroutine_handle<noop_coroutine_promise> 的别名)
  • 可转换为 coroutine_handle<>
  • 用于某些需要返回协程句柄但实际不需要操作的场景

7.5 生命周期管理

coroutine_handle 是一个非拥有句柄——它不自动管理协程帧的生命周期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ⚠️ 错误示例:忘记 destroy()
void leak() {
auto handle = some_coroutine(); // 假设 final_suspend 返回 suspend_always
handle.resume();
// handle 析构 → 协程帧泄露!
} // 需要调用 handle.destroy() 否则内存泄露

// ✅ 正确:RAII 管理
struct Generator {
std::coroutine_handle<promise_type> h_;

~Generator() {
if (h_) h_.destroy(); // 析构时自动销毁
}

// 禁止拷贝(避免 double destroy)
Generator(const Generator&) = delete;
Generator(Generator&& rhs) : h_(std::exchange(rhs.h_, nullptr)) {}
};

7.6 安全指南

注意点 说明
悬垂句柄 协程帧已销毁后继续使用 resume() 是未定义行为
double destroy 对同一帧调用两次 destroy() 是未定义行为
done() 的时机 done() 只在协程挂起到 final_suspend 后返回 true,其他情况可能不准确
线程安全 resume() / destroy() 在同一时刻只能由一个线程调用,否则数据竞争
promise 访问 final_suspend 返回 suspend_never 后访问 promise() 是未定义行为
1
2
3
4
5
6
7
8
// 安全使用模式
Generator<int> gen = counter();

while (!gen.h_.done()) { // 检查是否已完成
gen.h_.resume(); // 恢复
auto val = gen.h_.promise().value_; // 读取值
// ...
} // Generator 析构 → 自动 destroy()

第八章:co_yield 与生成器模式

8.1 co_yield 的本质

co_yield expr 是 C++20 协程中用于向调用者生成一系列值的机制。编译器将其展开为:

1
2
3
co_yield expr;
// 等价于:
co_await promise.yield_value(expr);

因此,yield_value() 返回的 awaitable 决定了 yield 之后的控制流——通常是 suspend_always 以挂起协程,让调用者读取值。

8.2 使用 promise.value_ 传递数据

先看一个使用 yield_value 在 promise 中存储值的示例(对应 Mazières 的 counter4):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <coroutine>
#include <iostream>

struct ReturnObject4 {
struct promise_type {
unsigned value_; // ← 存储传给调用者的值

ReturnObject4 get_return_object() {
return ReturnObject4{
.h_ = std::coroutine_handle<promise_type>::from_promise(*this)
};
}
std::suspend_never initial_suspend() { return {}; } // 立即开始
std::suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() {}
void return_void() {}

// co_yield i → 调用此函数
std::suspend_always yield_value(unsigned value) {
value_ = value; // 保存值到 promise
return {}; // 挂起,让调用者读取
}
};

std::coroutine_handle<promise_type> h_;
// 隐式转换为 handle,方便 main 调用
operator std::coroutine_handle<promise_type>() const { return h_; }
};

ReturnObject4 counter4() {
for (unsigned i = 0;; ++i)
co_yield i; // → promise.yield_value(i) → 保存 → 挂起
}

int main() {
auto handle = counter4(); // 立即开始,执行到 co_yield 0 挂起
auto& promise = handle.promise();

for (int i = 0; i < 3; ++i) {
std::cout << "counter4: " << promise.value_ << std::endl;
handle(); // resume → 继续循环 → 下一个 co_yield
}
handle.destroy();
}

输出

1
2
3
counter4: 0
counter4: 1
counter4: 2

8.3 构建泛型生成器 Generator

基于 Mazières 教程的最终示例,构建一个通用的生成器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <coroutine>
#include <exception>
#include <iostream>

template<typename T>
struct Generator {
// --- promise_type 定义 ---
struct promise_type {
T value_;
std::exception_ptr exception_;

Generator get_return_object() {
return Generator(handle_type::from_promise(*this));
}

// 惰性启动:等待第一次 resume 才执行
std::suspend_always initial_suspend() { return {}; }

// 执行完毕后挂起,让调用者检查 done()
std::suspend_always final_suspend() noexcept { return {}; }

void unhandled_exception() {
exception_ = std::current_exception();
}

void return_void() {}

// co_yield value → 保存值,挂起
template<std::convertible_to<T> From>
std::suspend_always yield_value(From&& from) {
value_ = std::forward<From>(from);
return {};
}
};

using handle_type = std::coroutine_handle<promise_type>;
handle_type h_;

explicit Generator(handle_type h) : h_(h) {}

// 不可拷贝(避免 double destroy)
Generator(const Generator&) = delete;
Generator(Generator&& rhs) noexcept
: h_(std::exchange(rhs.h_, nullptr)) {}

~Generator() {
if (h_) h_.destroy();
}

// 检查是否还有值(触发一次 resume)
explicit operator bool() {
fill();
return !h_.done();
}

// 获取下一个值(触发一次 resume)
T operator()() {
fill();
full_ = false; // 标记已消费
return std::move(h_.promise().value_);
}

private:
bool full_ = false;

void fill() {
if (!full_) {
h_(); // resume → 执行到下一个 co_yield 或 co_return
if (h_.promise().exception_)
std::rethrow_exception(h_.promise().exception_);
full_ = true;
}
}
};

// --- 使用示例 ---
Generator<unsigned> fibonacci() {
unsigned a = 0, b = 1;
while (true) {
co_yield a; // 生成当前值
auto next = a + b;
a = b;
b = next;
}
}

Generator<unsigned> range(unsigned max) {
for (unsigned i = 0; i < max; ++i)
co_yield i;
}

int main() {
std::cout << "Fibonacci: ";
auto fib = fibonacci();
for (int i = 0; i < 10; ++i) {
std::cout << fib() << " ";
}
std::cout << std::endl;

std::cout << "Range(0..4): ";
auto r = range(5);
while (r) {
std::cout << r() << " ";
}
std::cout << std::endl;

return 0;
}

输出

1
2
Fibonacci: 0 1 1 2 3 5 8 13 21 34
Range(0..4): 0 1 2 3 4

8.4 生成器调用时序图

sequenceDiagram
    participant Caller as 调用者 (main)
    participant Coro as 协程帧 (counter)
    participant Promise as promise_type
    participant Awaiter as suspend_always

    Note over Caller,Awaiter: 创建(惰性)
    Caller->>Coro: auto gen = counter()
    Coro->>Coro: 分配帧、构造 promise
    Coro->>Promise: get_return_object()
    Promise-->>Caller: 返回 Generator 对象
    Coro->>Promise: initial_suspend() -> suspend_always
    Note over Coro: 挂起(尚未执行循环体)

    Note over Caller,Awaiter: 第 1 次恢复
    Caller->>Coro: gen() 或 bool(gen)
    Coro->>Coro: 进入循环 i=0
    Coro->>Promise: co_yield 0 -> yield_value(0)
    Promise->>Promise: value_ = 0
    Promise->>Awaiter: return suspend_always{}
    Awaiter-->>Caller: 挂起,返回给调用者
    Caller->>Promise: 读取 value_ -> 0

    Note over Caller,Awaiter: 第 2 次恢复
    Caller->>Coro: gen()
    Coro->>Coro: 继续循环 i=1
    Coro->>Promise: co_yield 1 -> yield_value(1)
    Promise->>Promise: value_ = 1
    Promise->>Awaiter: return suspend_always{}
    Awaiter-->>Caller: 挂起
    Caller->>Promise: 读取 value_ -> 1

    Note over Caller,Awaiter: 第 3 次恢复(结束)
    Caller->>Coro: gen()
    Coro->>Coro: i=2, co_yield 2
    Coro->>Coro: i=3, 循环结束
    Coro->>Promise: co_return 调用 return_void()
    Coro->>Coro: 逆序销毁局部变量
    Coro->>Promise: final_suspend() -- 返回 suspend_always
    Note over Coro: 挂起(done() 为 true)
    Caller->>Coro: !gen -> 循环结束
    Caller->>Coro: gen 析构 -> h_.destroy()

8.5 C++23 std::generator

C++23 标准库引入了 std::generator<T>,提供了开箱即用的生成器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <generator>
#include <iostream>
#include <ranges>

std::generator<int> fib() {
int a = 0, b = 1;
while (true) {
co_yield a;
auto next = a + b;
a = b;
b = next;
}
}

int main() {
for (int x : fib() | std::views::take(10)) {
std::cout << x << " "; // 0 1 1 2 3 5 8 13 21 34
}
}

第九章:co_return 与协程完成

9.1 co_return 的两种形式

1
2
3
4
5
6
7
// 形式1:无返回值
co_return;
// → 调用 promise.return_void()

// 形式2:有返回值
co_return expr;
// → 调用 promise.return_value(expr)

return_void()return_value() 不能同时存在(编译错误)。

9.2 完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <coroutine>
#include <iostream>
#include <optional>

struct Task {
struct promise_type {
int result_;
std::exception_ptr exception_;

Task get_return_object() {
return Task(
std::coroutine_handle<promise_type>::from_promise(*this)
);
}

std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }

void return_value(int value) {
result_ = value; // co_return 的值
}

void unhandled_exception() {
exception_ = std::current_exception();
}
};

std::coroutine_handle<promise_type> h_;

explicit Task(std::coroutine_handle<promise_type> h) : h_(h) {}
Task(Task&& rhs) : h_(std::exchange(rhs.h_, nullptr)) {}
~Task() { if (h_) h_.destroy(); }

// 阻塞等待协程结果
int get() {
if (!h_.done()) h_.resume(); // 确保执行到 final_suspend
if (h_.promise().exception_)
std::rethrow_exception(h_.promise().exception_);
return h_.promise().result_;
}
};

// 计算斐波那契第 n 项
Task compute_fib(int n) {
if (n <= 1) co_return n;
int a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
int next = a + b;
a = b;
b = next;
}
co_return b;
}

int main() {
auto task = compute_fib(10);
int result = task.get();
std::cout << "fib(10) = " << result << std::endl; // 55
return 0;
}

9.3 final_suspend 的重要性

final_suspend() 的返回值选择至关重要:

返回值 后果
suspend_always 协程帧保持有效,调用者可读取 promise、检查 done()、配合 RAII 自动 destroy()
suspend_never 协程帧立即销毁,访问 promise = 未定义行为 ❌
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ❌ 错误:final_suspend 返回 suspend_never
struct BadTask {
struct promise_type {
int value_;
// ...
std::suspend_never final_suspend() noexcept { return {}; }
};
};

auto task = make_task(); // 协程执行并自动销毁
auto val = task.get(); // ❌ 协程帧已销毁,未定义行为!

// ✅ 正确:final_suspend 返回 suspend_always
struct GoodTask {
struct promise_type {
int value_;
// ...
std::suspend_always final_suspend() noexcept { return {}; }
};
};

9.4 done() 的正确使用

std::coroutine_handle::done() 返回协程是否已完成(即挂起在 final_suspend 之后):

1
2
3
4
5
6
7
8
9
10
auto h = counter5();

h.done(); // false(假设 initial_suspend 不挂起)
h.resume();
// 执行到某个 co_yield / co_await → 挂起
h.done(); // false(只是普通挂起)

// ... 多次 resume ...
// 最终 co_return → final_suspend → 挂起
h.done(); // true

重要done() 只在协程挂起到 final_suspend 时才返回 true。如果协程从未开始执行或挂起在普通点,done() 返回 false


第十章:对称转移(Symmetric Transfer)与协程链

10.1 什么是对称转移?

await_suspend 返回一个 coroutine_handle 时,控制权立即转移到那个协程,而不是返回给调用者。

1
2
3
4
5
6
7
不对称(Asymmetric):调用者 resume → 协程A → 挂起 → 返回调用者

调用者 resume → 协程B → ...

对称(Symmetric): 调用者 resume → 协程A → 挂起 → 直接恢复协程B → ...

协程B → 挂起 → 直接恢复协程A → ...

优点

  • 避免深度嵌套的 resume 调用导致的栈溢出
  • 协程之间可以直接传递控制权,无需中间人
  • 适合实现协程调度器、管道(pipeline)

10.2 为什么需要对称转移?

考虑一个简单的管道场景:协程 A 生成数据,协程 B 处理数据。

如果使用不对称转移:

1
2
3
4
5
6
7
8
9
// 调用者负责调度
void caller() {
auto hA = producer();
auto hB = consumer();
while (!hA.done()) {
hA.resume(); // A 生产一个值 → 挂起
hB.resume(); // B 消费一个值 → 挂起
}
}

如果 A 和 B 之间相互传递控制权,而不经过调用者,就用到了对称转移:

1
2
3
4
5
6
// await_suspend 返回 coroutine_handle<>
// 直接跳转到目标协程,不返回给调用者
std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept {
// 选择下一个要执行的协程
return next_coroutine_;
}

10.3 对称转移示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <coroutine>
#include <iostream>

// 保存外部 handle 的 awaiter
struct TransferAwaiter {
std::coroutine_handle<> target_;

bool await_ready() noexcept { return false; }

// ⭐ 返回 coroutine_handle → 对称转移
std::coroutine_handle<> await_suspend(
std::coroutine_handle<> /*current*/
) noexcept {
return target_; // 立即恢复 target_,不返回给 current 的调用者
}

void await_resume() noexcept {}
};

struct Task {
struct promise_type {
Task get_return_object() {
return Task{
std::coroutine_handle<promise_type>::from_promise(*this)
};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};

std::coroutine_handle<promise_type> h_;
~Task() { if (h_) h_.destroy(); }
};

// 协程 A:在 A 和 B 之间交替
Task coro_a(std::coroutine_handle<>& a_handle,
std::coroutine_handle<>& b_handle) {
for (int i = 0; i < 3; ++i) {
std::cout << " A: " << i << " (准备切换到 B)" << std::endl;
co_await TransferAwaiter{b_handle}; // ⭐ 对称转移到 B
std::cout << " A: 从 B 切换回来" << std::endl;
}
}

// 协程 B
Task coro_b(std::coroutine_handle<>& a_handle,
std::coroutine_handle<>& b_handle) {
for (int i = 0; i < 3; ++i) {
std::cout << " B: " << i << " (准备切换到 A)" << std::endl;
co_await TransferAwaiter{a_handle}; // ⭐ 对称转移到 A
std::cout << " B: 从 A 切换回来" << std::endl;
}
}

int main() {
std::coroutine_handle<> a_h, b_h;

// 启动两个协程(initial_suspend = suspend_never → 立即执行)
auto a = coro_a(a_h, b_h);
auto b = coro_b(a_h, b_h);

// 保存句柄(协程已执行到第一个 TransferAwaiter,已挂起)
a_h = a.h_;
b_h = b.h_;

std::cout << "main: 恢复 A,启动交替执行" << std::endl;
a_h.resume(); // 启动 A → A 打印 → 切换到 B → B 打印 → 切换到 A → ...
// A 和 B 交替执行,main 只 resume 了一次
std::cout << "main: 完成" << std::endl;
return 0;
}

输出

1
2
3
4
5
6
7
8
9
10
11
main: 恢复 A,启动交替执行
A: 0 (准备切换到 B)
B: 0 (准备切换到 A)
A: 从 B 切换回来
A: 1 (准备切换到 B)
B: 1 (准备切换到 A)
A: 从 B 切换回来
A: 2 (准备切换到 B)
B: 2 (准备切换到 A)
A: 从 B 切换回来
main: 完成

10.4 调用栈分析

不对称转移的调用栈:

1
2
3
4
5
frame 0: main → resume(hA)
frame 1: coroA → co_await → await_suspend → return void
frame 0: main ← ← ← ←
frame 0: main → resume(hB) ← 再 resume B,栈深度始终为 1
frame 1: coroB → ...

对称转移的调用栈:

1
2
3
4
frame 0: main → resume(hA)
frame 1: coroA → co_await → await_suspend → return hB
frame 1: → resume(hB) ← 直接跳转,栈深度增加!
frame 2: coroB → co_await → ...

表面上看对称转移增加了一个栈帧,但注意:对称转移避免了不断的 resume/return 循环。更重要的是,在复杂的调度场景中(如协程链 A → B → C → D → …),不对称转移会导致调用者的调度函数深度嵌套,而对称转移让协程之间直接传递控制权。

注意:在某些实现中,返回 coroutine_handleawait_suspend 可能被编译器优化为 tail call,从而避免栈的增长。


第十一章:实际应用模式

模式一:惰性生成器

已在第8章完整实现 Generator<T>。核心特征:

  • initial_suspend() 返回 suspend_always(惰性)
  • final_suspend() 返回 suspend_always(安全访问值)
  • yield_value() 在 promise 中保存值并挂起
  • RAII 封装自动管理 destroy()

模式二:跨线程切换

基于知乎文章中的跨线程示例,实现一个从当前线程切换到新线程的 awaiter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <coroutine>
#include <iostream>
#include <thread>
#include <chrono>

// 跨线程切换 Awaiter
struct SwitchToNewThread {
std::jthread& target_thread_;

bool await_ready() noexcept { return false; }

void await_suspend(std::coroutine_handle<> h) noexcept {
std::jthread& out = target_thread_;
if (out.joinable())
throw std::runtime_error("Thread already set");

out = std::jthread([h] {
h.resume(); // 在新线程中恢复协程
});
// ⚠️ 此时 this 可能已销毁(协程已在新线程运行)
}

void await_resume() noexcept {}
};

struct Task {
struct promise_type {
Task get_return_object() {
return Task{
std::coroutine_handle<promise_type>::from_promise(*this)
};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};

std::coroutine_handle<promise_type> h_;
~Task() { if (h_) h_.destroy(); }
};

// 跨线程协程
Task cross_thread_demo(std::jthread& worker) {
std::cout << "协程在 线程 " << std::this_thread::get_id()
<< " 开始执行" << std::endl;

co_await SwitchToNewThread{worker};

// ⭐ 此处已在新的工作线程上执行
std::cout << "协程在 线程 " << std::this_thread::get_id()
<< " 恢复执行" << std::endl;

co_return;
}

int main() {
std::jthread worker;
cross_thread_demo(worker);

// 等待协程完成(worker 析构时 join)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "main 完成" << std::endl;
return 0;
}

可能的输出

1
2
3
协程在 线程 12345 开始执行
协程在 线程 67890 恢复执行
main 完成

跨线程协程调用时序图

sequenceDiagram
    participant Main as main() 线程
    participant Coro as 协程帧
    participant Promise as promise_type
    participant Awaiter as 线程切换 Awaiter
    participant Thread as 新线程

    Note over Main,Thread: 创建阶段
    Main->>Coro: 调用协程函数
    Coro->>Coro: 分配帧
    Coro->>Promise: 构造 promise
    Promise->>Promise: get_return_object()
    Promise->>Coro: initial_suspend() → suspend_never
    Note over Coro: 协程开始执行
打印 "线程 A" Note over Main,Thread: 挂起与转移 Coro->>Awaiter: co_await SwitchToNewThread(worker) Awaiter->>Awaiter: await_ready() → false Awaiter->>Awaiter: 保存 handle Awaiter->>Thread: std::jthread(lambda -> handle.resume()) Note over Awaiter: ⚠ 发布 handle 后
this 可能已销毁 Awaiter-->>Main: 返回给调用者 Note over Main,Thread: 恢复阶段 Thread->>Coro: handle.resume() Note over Coro: 协程在新线程恢复
打印 "线程 B" Coro->>Coro: co_return; Coro->>Promise: return_void() Coro->>Coro: 逆序销毁局部变量 Coro->>Promise: final_suspend() → suspend_always Note over Coro: 挂起(done() 为 true) Note over Coro: 自动销毁

模式三:await_transform 实现优先级调度

基于 Josuttis 的优先级调度示例,展示 await_transform 的威力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <coroutine>
#include <iostream>
#include <queue>
#include <functional>

// 优先级请求类型
enum class Priority { low = 0, normal = 1, high = 2 };

struct CoroPrioRequest {
Priority prio_;
};

// 协程优先级调度器
class CoroPrioScheduler {
public:
// 注册协程
struct promise_type {
CoroPrioScheduler* sched_ = nullptr;
Priority prio_ = Priority::normal;

promise_type() : sched_(&default_scheduler()) {}

CoroPrioScheduler get_return_object() {
return CoroPrioScheduler(
std::coroutine_handle<promise_type>::from_promise(*this)
);
}

std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }

// ⭐ await_transform:截获 co_await CoroPrioRequest{}
auto await_transform(CoroPrioRequest pr) {
prio_ = pr.prio_;
auto hdl = std::coroutine_handle<promise_type>::from_promise(*this);
sched_->changePrio(hdl, pr.prio_);
return std::suspend_always{};
}
};

using handle_type = std::coroutine_handle<promise_type>;
handle_type h_;

explicit CoroPrioScheduler(handle_type h) : h_(h) {}
~CoroPrioScheduler() { if (h_) h_.destroy(); }

// 改变优先级
void changePrio(handle_type h, Priority p) {
// 实际调度逻辑...
std::cout << "协程优先级改为: " << static_cast<int>(p) << std::endl;
}

static CoroPrioScheduler& default_scheduler() {
static CoroPrioScheduler instance(nullptr);
return instance;
}

private:
CoroPrioScheduler(CoroPrioScheduler*) {} // 仅用于默认调度器
};

// 使用协程
CoroPrioScheduler prio_demo() {
std::cout << "默认优先级执行中..." << std::endl;

co_await CoroPrioRequest{Priority::high};

std::cout << "高优先级执行中..." << std::endl;

co_await CoroPrioRequest{Priority::low};

std::cout << "低优先级执行中..." << std::endl;

co_return;
}

int main() {
auto task = prio_demo();
std::cout << "main 完成" << std::endl;
return 0;
}

第十二章:常见陷阱与最佳实践

12.1 悬垂引用(Dangling Reference)

按引用传递参数是协程中最常见的陷阱:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ❌ 危险:按引用传递
Task process(const Data& data) {
co_await delay(1s); // 挂起点
use(data); // data 可能已悬垂!
}

void caller() {
Data d;
auto t = process(d); // 引用 d
// d 在此处析构!
// t 恢复时访问悬垂引用
}

// ✅ 安全:按值传递
Task process(Data data) { // 复制到协程帧中
co_await delay(1s);
use(data); // 安全,data 在协程帧中
}

规则协程参数始终按值传递。如果必须按引用传递,确保引用对象的生命周期超过协程的整个生命周期。

12.2 句柄生命周期管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ❌ 错误:忘记 destroy
void leak() {
auto h = make_coro(); // final_suspend 返回 suspend_always
h.resume();
// h 析构 → 协程帧泄露!
}

// ✅ 正确:使用 RAII 封装
struct MyTask {
std::coroutine_handle<promise_type> h_;
~MyTask() { if (h_) h_.destroy(); }
MyTask(const MyTask&) = delete; // 禁止拷贝
MyTask(MyTask&& rhs) noexcept // 允许移动
: h_(std::exchange(rhs.h_, nullptr)) {}
};

// ✅ 正确:使用智能指针
auto h = make_coro();
std::unique_ptr<void, decltype([](void* p) {
std::coroutine_handle<>::from_address(p).destroy();
})> guard(h.address());

12.3 final_suspend 返回 suspend_never 的风险

1
2
3
4
5
6
7
// ❌ 危险:final_suspend 返回 suspend_never
void dangerous() {
auto h = make_coro(); // 执行完 → final_suspend → 自动销毁
// h 已悬垂!任何操作都是 UB
h.resume(); // ❌ 未定义行为!
auto& p = h.promise(); // ❌ 未定义行为!
}

规则:除非你完全确定协程结束后不再需要访问 promise 或句柄,否则 final_suspend 永远返回 suspend_always

12.4 异常安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <coroutine>
#include <exception>

struct SafeTask {
struct promise_type {
std::exception_ptr exception_;

// ...

void unhandled_exception() {
exception_ = std::current_exception(); // 保存异常
}

// 在 get() 中重新抛出
};

int get() {
if (!h_.done()) h_.resume();
if (h_.promise().exception_)
std::rethrow_exception(h_.promise().exception_);
return h_.promise().result_;
}
};

// 使用
SafeTask faulty() {
throw std::runtime_error("出错了!");
co_return 42;
}

int main() {
try {
auto t = faulty();
int val = t.get(); // → 重新抛出异常
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl; // 出错了!
}
}

12.5 内存分配优化

C++20 标准允许编译器在某些条件下优化掉协程帧的堆分配:

1
2
3
4
5
6
7
8
// 条件:
// 1. 协程帧的生存期完全包含在调用者的生存期内
// 2. 编译器在调用点知道帧的大小

// 优化前:堆分配
Task example() { co_return; }

// 编译器可优化为:栈分配(或直接内联)

此外,可以自定义协程帧的分配器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct MyPromise {
// 自定义分配器
static void* operator new(std::size_t size) {
return my_pool.allocate(size);
}

static void operator delete(void* ptr, std::size_t size) {
my_pool.deallocate(ptr, size);
}

// 形参感知的分配(leading allocator convention)
template<typename... Args>
static void* operator new(std::size_t size, Args&&... args) {
// 根据参数选择合适的分配策略
return ::operator new(size);
}
};

12.6 协程是"库作者工具"

如知乎文章所强调的,C++20 协程的设计面向的是库作者而非普通开发者。日常使用中:

你的角色 建议
应用开发者 使用现成的协程库(如 async_simple、cppcoro、Folly::coro)
库作者 学习本文档,实现自己的 promise_type 和 awaiter 类型
学习者 从生成器开始,逐步深入协程调度器

12.7 快速检查清单

  • [ ] 参数按值传递(避免悬垂引用)
  • [ ] final_suspend() 标记为 noexcept
  • [ ] 使用 RAII 管理 destroy()(或明确谁负责销毁)
  • [ ] final_suspend() 返回 suspend_always(除非有特殊理由)
  • [ ] 禁止拷贝返回对象(避免 double destroy)
  • [ ] await_suspend 中发布 handle 后不再访问 *this
  • [ ] 跨线程时确保 resume()/destroy() 不会并发执行
  • [ ] return_void()return_value() 不共存

附录

A. 标准库 头文件一览

组件 说明
std::coroutine_traits<R, Args...> 协程特征,确定 promise_type
std::coroutine_handle<Promise> 协程句柄模板
std::coroutine_handle<> 类型擦除的协程句柄
std::noop_coroutine_promise 无操作协程承诺类型
std::noop_coroutine_handle coroutine_handle<noop_coroutine_promise> 的别名
std::noop_coroutine() 创建无操作协程句柄
std::suspend_never 从不暂停的可等待体
std::suspend_always 总是暂停的可等待体

B. 各资料特色对比

资料源 特色 适合读者
cppreference.com 权威准确,接口完整 作为参考手册查阅
Mazières 教程 渐进示例,逐步构建 想理解底层机制的初学者
Simon Tatham 教程 系统设计视角,三种类型分析 想写自定义协程系统的开发者
知乎文章 中文深度解读,有栈/无栈辨析 中文读者,快速建立认知框架
Lewis Baker 系列 最深入的技术分析 进阶/专家级开发者

C. 关键术语中英文对照

英文 中文 说明
coroutine 协程 可挂起/恢复的函数
stackless coroutine 无栈协程 C++20 标准采用的方案
coroutine frame / state 协程帧 / 协程状态 堆上分配的存储区域
promise object 承诺对象 协程内部控制器
promise_type 承诺类型 必须嵌套在返回类型中的类型
coroutine handle 协程句柄 外部操控协程的句柄
return object 返回对象 调用者看到的返回值类型
awaitable 可等待体 可用作 co_await 操作数的类型
awaiter 等待器 实现了 await_ready/suspend/resume 的类型
awaiter protocol 等待协议 co_await 的编译器展开规则
await_transform 等待转换 promise 中转换 co_await 操作数的方法
suspend 挂起 暂停协程执行
resume 恢复 继续协程执行
symmetric transfer 对称转移 协程之间直接传递控制权
asymmetric transfer 不对称转移 协程挂起后返回给恢复者
coroutine_traits 协程特征 确定 promise_type 的元函数

D. 推荐阅读


文档完成日期:2026-07-09
参考资料版本:基于 C20 标准(最终草案),部分提及 C23 特性
反馈与改进:如有发现错误或遗漏,欢迎指正

Buy me a coffee please.