C++20 引入了协程(coroutines)这一强大的语言特性,为异步编程、生成器、延迟计算等场景提供了更加直观和高效的实现方式。本文将从协程的实现机制、关键语法、常见使用场景以及性能优化等方面进行详细阐述。
一、协程的基本概念
协程是一种比线程更轻量级的“协作式多任务”机制。与线程的抢占式调度不同,协程通过显式的挂起(co_await、co_yield、co_return)来让出执行权,等待外部事件或条件满足后再恢复。协程的执行流在编译期被拆分为若干“挂起点”,在运行时通过状态机形式完成。
C++ 协程的核心组件包括:
- promise_type:协程的承诺对象,负责维护协程的状态、返回值以及异常处理。
- handle_type:协程句柄,用于控制协程的生命周期(resume、destroy、done 等)。
- awaiter:等待对象,实现了
await_ready、await_suspend、await_resume三个成员函数,用来定义协程挂起和恢复的行为。
二、关键语法与实现细节
1. co_return
co_return 用于返回协程的最终值。它会调用 promise_type::return_value 或 return_void。与普通函数不同,co_return 并不立即结束协程,而是触发协程句柄的 destroy 过程。
std::future <int> async_sum(int a, int b) {
co_return a + b; // 等价于 return a + b;
}
2. co_yield
co_yield 用于生成器(generator)模式,返回一个值后挂起协程,等待下次 resume。
std::generator <int> fibonacci(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
int next = a + b;
a = b;
b = next;
}
}
3. co_await
co_await 是协程最核心的挂起机制。它接受一个 awaiter 对象,调用 await_ready() 判断是否立即完成;若不完成,则调用 await_suspend(),并在适当时机通过 await_resume() 恢复。
std::future <void> async_read(std::string file) {
auto data = co_await async_io::read(file); // awaitable
process(std::move(data));
}
三、协程与 Awaitable 的设计
1. Awaitable 的结构
任何可 co_await 的类型必须满足 Awaitable 协议:
struct Awaitable {
bool await_ready();
void await_suspend(std::coroutine_handle<> h);
T await_resume();
};
- await_ready:如果返回
true,协程会立即继续执行;否则进入挂起状态。 - await_suspend:传入当前协程句柄,协程挂起后会调用此函数。此函数通常将协程句柄保存到异步事件源,以便事件触发时恢复。
- await_resume:事件完成后执行,用于返回异步结果。
2. 例子:简易异步 I/O
struct AsyncRead {
std::string filename;
std::string buffer;
bool await_ready() noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
// 模拟异步读取
std::thread([=]() mutable {
std::this_thread::sleep_for(std::chrono::seconds(1));
buffer = "文件内容";
h.resume(); // 恢复协程
}).detach();
}
std::string await_resume() noexcept { return buffer; }
};
std::future<std::string> read_file(std::string fn) {
AsyncRead ar{fn};
co_return co_await ar;
}
四、协程的典型应用场景
| 场景 | 典型用途 | 示例 |
|---|---|---|
| 生成器 | 延迟生成序列、迭代器 | `generator |
| range(int n)` | ||
| 异步 I/O | 网络、磁盘读写 | co_await async_socket::recv() |
| 协作式调度 | 实现轻量级线程、事件循环 | coroutine_handle 管理任务 |
| 状态机 | 复杂业务流程 | co_await state_transition() |
| 管道式流 | 数据流处理 | co_yield transform(co_await source) |
五、性能与资源管理
虽然协程在 C++20 标准中是轻量级的,但实现细节会影响性能:
- 状态机大小:协程的状态机(promise_type 对象)会随着挂起点数量增加而增大。尽量减少不必要的成员。
- 堆分配:默认情况下,协程句柄在堆上分配。可使用
std::coroutine_handle::from_promise手动控制生命周期,或采用自定义分配器。 - 异常处理:异常会传播到 promise_type,若未捕获,协程会被自动销毁。确保
promise_type::final_suspend正确处理异常。 - 缓存与对齐:对于高频调用的协程,考虑使用
[[no_unique_address]]或alignas优化内存布局。
六、协程与线程的比较
| 维度 | 协程 | 线程 |
|---|---|---|
| 调度方式 | 协作式(显式挂起) | 抢占式(内核调度) |
| 资源占用 | 轻量级(栈可压缩) | 重量级(线程栈 1MB+) |
| 并发模型 | 单线程多任务 | 多线程并行 |
| 异常传播 | 通过 promise 机制 | 通过线程间同步 |
| 适用场景 | I/O 密集、生成器、状态机 | CPU 密集、并行计算 |
七、未来展望
C++23 对协程进行了若干改进,例如:
std::generator的标准化std::async与协程的结合- 更完善的 awaitable 类型约束
- 与
std::ranges的深度集成
未来,协程将成为 C++ 异步编程的核心抽象,结合模板元编程和概念(concepts)可以实现更安全、更高效的异步代码。掌握协程不仅能提升程序性能,还能显著降低异步代码的复杂度。
结语
C++20 协程的引入,为开发者提供了强大而灵活的工具,能够以更接近同步的语法实现异步、生成器和协作式多任务。通过深入理解其实现机制与使用模式,能够在实际项目中充分发挥协程的优势,实现高性能、可维护的 C++ 应用。