C++20的协程(Coroutines)如何使用?(入门示例)

协程是用户态可挂起恢复的函数,C++20采用无栈协程,编译器将其重写为状态机;需含co_await/co_yield/co_return之一,返回类型须定义promise_type以支持协程协议。

协程不是线程,也不依赖操作系统调度;它是用户态的、可挂起和恢复的函数,适合写异步 I/O、生成器、状态机等逻辑清晰但传统回调写法容易嵌套过深的场景。C++20 的协程是无栈协程(stackless),编译器会将协程函数重写为状态机,由你控制内存布局和执行时机。

第一步:识别协程函数——三个关键字缺一不可

一个函数要成为协程,必须在函数体中至少出现以下任一关键词:

  • co_await:等待某个“可等待对象”(如自定义 awaiter、std::suspend_always)
  • co_yield:产出值并挂起,常用于生成器
  • co_return:结束协程(可能触发清理或回调)

只要用了其中一个,编译器就按协程处理——自动改写函数签名、生成 promise 对象、管理状态机。注意:函数返回类型必须满足特定要求(见下一步)。

第二步:返回类型需支持协程协议——关键在 promise_type

协程函数的返回类型(比如 Generator)必须嵌套定义 promise_type,该类型负责协程生命周期的关键行为:

  • get_return_object():返回协程句柄(如 Generator 实例),供调用者接收
  • initial_suspend():决定协程启动时是否立刻挂起(常返回 std::suspend_alwaysstd::suspend_never
  • final_suspend():协程结束前是否挂起(影响资源释放时机)
  • unhandled_exception():捕获未处理异常
  • return_void() / return_value(T):对应 co_return 的不同写法

标准库没提供通用协程类型,你需要自己定义(或使用第三方如 cppcoro)。初学建议从简单生成器入手。

第三步:动手写一个整数生成器(Generator

下面是最小可行示例,实现一个每次调用 next() 返回下一个斐波那契数的协程:

template
class Generator {
  struct promise_type;
  using handle_type = std::coroutine_handle

  handle_type h_;

public:
  Generator(handle_type h) : h_(h) {}
  Generator(Generator&& g) noexcept : h_(std::exchange(g.h_, {})) {}
  ~Generator() { if (h_) h_.destroy(); }

  T next() {
    h_.resume();
    return h_.promise().current_value;
  }

  struct promise_type {
    T current_value;
    std::suspend_always initial_suspend() { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    Generator get_return_object() { return Generator{handle_type::from_promise(*this)}; }
    void return_void() {}
    void unhandled_exception() { std::terminate(); }
    std::suspend_always yield_value(T value) {
      current_value = value;
      return {};
    }
  };
};

// 协程函数
Generator fibonacci() {
  int a = 0, b = 1;
  co_yield a;
  co_yield b;
  while (true) {
    int next = a + b;
    co_yield next;
    a = b;
    b = next;
  }
}

使用方式:

auto gen = fibonacci();
for (int i = 0; i < 10; ++i) {
  std::cout << gen.next() << " "; // 输出前10个斐波那契数
}

第四步:理解 co_await —— 自定义 awaiter 的核心三接口

当你写 co_await expr,expr 类型需提供:

  • await_ready():返回 bool,true 表示无需挂起,直接继续
  • await_suspend(handle):挂起时调用,可安排 handle 在未来某时 resume(例如投递到线程池、注册到 epoll)
  • await_resume():resume 后返回给 co_await 表达式的值

这是实现异步 I/O 的入口。例如封装一个 sleep_for awaiter,内部用 std::thread::sleep_for + std::jthread 或定时器唤醒 handle,就能写出 co_await sleep_for(500ms) 这样的代码。

不复杂但容易忽略:协程对象的内存必须手动管理(或用智能指针包装 handle),且 promise 对象默认分配在协程帧中(栈上),若需跨 suspend 持久化,得重载 operator new 做堆分配。