C++ 进阶(27):协程实战——C++20 协程基础用法、生成器、异步 I/O
更新时间:2026-09-01。本文是
languages/cpp/intermediate/进阶第 27 篇,接 位操作。C++20 引入了协程(coroutine),支持暂停和恢复执行。本文讲基础概念、简单生成器、以及异步 I/O 例子。
本文要回答的问题
- 什么是协程?协程和线程有什么区别?
- C++20 协程核心部件是什么?promise、return_type、awaiter?
co_await、co_yield、co_return分别是什么?- 怎么写一个简单的整数生成器?
一、协程 vs 线程
协程:用户态调度,一个线程可以跑多个协程,上下文切换快
线程:内核态调度,操作系统调度,上下文切换慢
Go goroutine 就是协程,运行在 M 上,G 是协程。C++20 协程给你底层控制。核心区别: 协程可以主动暂停,把控制权交出去,然后在需要的时候再恢复。
二、C++20 协程核心概念
C++20 协程是无栈协程,用户自己管理状态。每个协程需要定义三个部分:
promise_type:协程承诺类型,定义开始结束、返回值、挂起行为return_type:返回给调用者的类型awaiter:挂起等待,定义等待的时候做什么
关键字:
co_await expr—— 等待某个操作完成,可能挂起协程co_yield val—— 产出一个值,挂起协程co_return val—— 协程返回,结束
三、最简单的整数生成器
cpp
#include <coroutine>
#include <iostream>
#include <optional>
template<typename T>
struct Generator {
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
struct promise_type {
std::optional<T> current;
Generator get_return_object() {
return Generator{handle_type::from_promise(*this)};
}
std::suspend_always initial_suspend() {
return {}; // 首次执行前挂起
}
std::suspend_always final_suspend() noexcept {
return {}; // 结束后挂起
}
void unhandled_exception() {
// 处理异常
std::terminate();
}
std::suspend_always yield_value(T value) {
current = value;
return {}; // 产出后挂起
}
void return_void() {}
};
handle_type handle;
bool next() {
handle.resume();
return !handle.done();
}
T value() {
return handle.promise().current.value();
}
~Generator() {
if (handle) handle.destroy();
}
// 不支持拷贝,只支持移动
Generator(const Generator&) = delete;
Generator& operator=(const Generator&) = delete;
Generator(Generator&& other) : handle(other.handle) {
other.handle = nullptr;
}
};
// 使用
Generator<int> fib(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; i++) {
co_yield a + b;
int next = a + b;
a = b;
b = next;
}
}
int main() {
for (auto gen = fib(10); gen.next(); ) {
std::cout << gen.value() << " ";
}
// 输出:1 2 3 5 8 13 21 34 55 89
}说明:
co_yield产出值后挂起next()调用resume()恢复- 循环到
co_return完成,done()返回 true
四、co_await 等待异步操作
cpp
// 最简单的 awaiter
struct Awaitable {
bool await_ready() {
return false; // false → 挂起
}
void await_suspend(std::coroutine_handle<> h) {
// 异步操作完成后调用 h.resume()
}
auto await_resume() {
return result; // 返回结果
}
};三个方法:
await_ready()—— 准备好了吗?true 不挂起,直接执行await_suspend(h)—— 挂起后在这里安排异步操作,完成后 resume(h)await_resume()—— 恢复后返回结果
五、常见使用场景
- 生成器/迭代器:上面的例子,co_yield 产出值
- 异步 I/O:等待网络读写完成不阻塞线程
- 状态机:每个状态挂起,恢复到下个状态
- 并发任务:多个任务在一个线程上交替执行
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 忘记销毁协程句柄 | 内存泄漏 | handle.destroy() |
| 协程完成后还 resume | UB | 检查 handle.done() |
| promise 类型写错 | 编译错误看不懂 | 必须叫 promise_type,接口要对 |
| 协程中抛出异常不处理 | terminate | 实现 unhandled_exception |
相关与延伸
下一篇:模块系统——C++20 modules 基础;C++20 协程机制深入,见 协程机制。
一句话总结
C++20 协程:无栈协程,支持暂停和恢复;核心是 promise_type,定义初始挂起、最终挂起、yield、返回;co_yield 产出值,co_await 等待异步操作,co_return 返回;最简单的应用是生成器,co_yield 逐个产出;协程比线程更轻量,上下文切换更快;需要手动管理句柄生命周期,避免内存泄漏。