C++20引入了协程的概念,但是对于首次接触的开发者来说,理解起来还是有些吃力,本文尝试一次性讲通协程的基本概念以及用法。

  1. 为什么需要协程?
  2. 协程的基本概念
  3. C++20 协程的三个关键字
  4. 协程的组成部件详解

  5. 完整执行流程逐步解析
  6. 数据流转详解
  7. 编译器内部变换揭秘

  8. 内存管理与生命周期
  9. suspend_always 与 suspend_never 的本质
  10. 自定义 Awaiter 实战
  11. 协程 vs 线程 vs 回调
  12. 常见陷阱与注意事项
  13. 进阶:异步任务协程

  14. 全景脑图:所有概念的关系图
  15. 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

这是协程函数的返回类型,由你自己定义,名字完全自由(叫 TaskFiberLazy 都行)。

作用

  1. 作为协程函数的返回值,让调用方持有对协程的控制权
  2. 必须包含一个名为 promise_type 的内嵌结构体(这是 C++ 标准规定的固定名字)
  3. 通常持有一个 coroutine_handle,用于控制协程的恢复和销毁
  4. 对外暴露用户友好的接口(如 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_yieldco_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_yieldco_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 = 1
  • i = 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=5co_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
                                                        │ 读取
                                                        ▼
                                                       42

co_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_alwaysfalse一定挂起
suspend_nevertrue一定不挂起,跳过 await_suspend

当你用这两者搭配 initial_suspend/final_suspend

initial_suspendfinal_suspend典型用途
suspend_alwayssuspend_alwaysGenerator(懒惰求值,手动管理生命周期)
suspend_neversuspend_alwaysTask(立即执行异步任务,等待完成)
suspend_neversuspend_neverFire-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 final

15. FinalSuspendAwaiter vs CoAwaitAwaiter:如何区分?

初学者最容易混淆的一点:FinalSuspendAwaiterCoAwaitAwaitersuspend_always 都有 await_ready/await_suspend/await_resume 三个函数,它们到底分别在什么时候触发?

15.1 一句话区分

结构体所处位置触发者触发时机
FinalSuspendAwaiterpromise_type 内部这个协程自己这个协程的协程体跑完
CoAwaitAwaiterTask 本体内部外层协程外层协程 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 存入 continuationFinalSuspendAwaiter 取出 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;
};

标签: none

添加新评论