第二十九章 协程与异步入门(C++20)

← 返回目录

本章目标:理解协程的核心概念、用生成器模式体验 co_yield、理解 co_await 与异步模型、了解标准库现状。 协程是 C++20 最有野心也最"不完整"的特性——先懂思想。

29.1 什么是协程(人话)

协程 = 可以"暂停/恢复"的函数。 普通函数:调用 → 一路跑到 return,无法中途停。 协程:可以执行到某个点"挂起",把控制权交还调用者, 将来某时刻"从挂起点继续"。

三个新关键字:

co_await   挂起,等待某个异步操作完成
co_yield   产出一个值,然后挂起(生成器)
co_return   结束协程

代价与门槛:

(promise_type 等)——这部分又长又绕

工程上请用现成库(asio、cppcoro、或 C++26 演进)

29.2 生成器:最直观的协程用法(co_yield)

// 一个"惰性产出"的生成器:不一次性算完,用多少算多少
Generator<int> range(int from, int to) {
    for (int i = from; i < to; ++i)
        co_yield i;            // 产出 i 并挂起
}

for (int x : range(0, 5))      // 每次循环"唤醒"一次
    std::println("{}", x);     // 打印 0 1 2 3 4

对比普通函数返回 vector:

29.3 最小生成器框架(教材级实现)

C++20 协程需要一个"promise 类型"(编译器约定的接口):

template <typename T>
class Generator {
public:
    struct promise_type;              // 编译器要求的名字
    using handle = std::coroutine_handle<promise_type>;

    struct promise_type {
        T current;
        struct FinalAwaiter {         // 结束时挂起
            bool await_ready() const noexcept { return false; }
            void await_suspend(handle) const noexcept {}
            void await_resume() const noexcept {}
        };
        Generator get_return_object() { return Generator{handle::from_promise(*this)}; }
        std::suspend_always initial_suspend() const noexcept { return {}; }
        FinalAwaiter final_suspend() const noexcept { return {}; }
        void return_void() const noexcept {}
        void unhandled_exception() { std::terminate(); }  // 教材简化
        std::suspend_always yield_value(T value) {
            current = std::move(value);      // 存下产出的值
            return {};                        // 挂起
        }
    };

    struct Iterator {
        handle h;
        bool operator!=(const Iterator&) const noexcept { return !h.done(); }
        void operator++() { h.resume(); }     // 唤醒协程
        T& operator*() { return h.promise().current; }
    };

    Iterator begin() { h.resume(); return {h}; }
    Iterator end() { return {handle{} }; }
    Generator(Generator&& o) noexcept : h(o.h) { o.h = nullptr; }
    ~Generator() { if (h) h.destroy(); }
private:
    explicit Generator(handle h) : h(h) {}
    handle h;
};

这是"能跑"的最小版本,理解即可。工程上:

29.4 无限生成器(协程的杀手锏)

Generator<unsigned long long> fibonacci() {
    unsigned long long a = 0, b = 1;
    while (true) {
        co_yield a;                  // 无限序列!
        unsigned long long t = a + b;
        a = b; b = t;
    }
}

int count = 0;
for (auto v : fibonacci()) {
    if (count++ >= 10) break;        // 用多少算多少
    std::println("{}", v);
}

普通函数做不到"返回无限序列"(内存会爆炸), 生成器可以——这就是惰性求值的力量。

29.5 co_await:异步等待(概念)

// 概念示意(需要异步框架,如 asio)
auto result = co_await async_read(file);   // 挂起,数据到了再继续

执行流程:

  1. 调用 async_read → 发起 IO → 挂起协程
  2. 控制权回到"事件循环"(主线程继续干别的)
  3. IO 完成 → 回调唤醒协程 → 从 co_await 处继续

好处:异步代码写成"顺序"样子,没有回调地狱。

C++20 只给了机制(awaiter 接口), 标准库的"官方异步 IO"仍未完成; 实际项目用 asio 等库。现在只需要懂概念。

29.6 协程的性能特征

开销约几十纳秒~几百纳秒(比线程切换快 10~100 倍)

29.7 陷阱清单

  1. 协程函数里不能用 return value(要 co_return)
  2. 忘了 final_suspend 挂起 → 协程未正常销毁
  3. 捕获引用变量,协程挂起后变量销毁 → 悬空
  4. 手工管理 handle 不 destroy → 泄漏
  5. 在不需要时误用协程(简单循环用普通函数)
  6. 依赖具体实现细节(promise_type 接口版本差异)
  7. 调试困难:调用栈是"挂起点"不是"调用点"

本章小结

练习题(配套测试:测试_第29章_协程.cpp)


  1. 运行生成器示例,理解暂停/恢复。
  2. 写 range(a, b, step) 生成器。
  3. 用生成器产出"质数序列"(惰性筛)。
  4. 写一个生成器输出 1..N 的平方。
  5. 对比生成器和 vector 的内存占用(思路题)。