在 C++20 之前,异步编程通常依赖于回调、线程、事件循环等方式,开发者需要自己手动管理状态机、上下文切换等细节。C++20 引入协程(Coroutines)后,这一切变得更加自然。本文将从协程的基本概念、实现原理、标准库支持以及实际编码示例,帮助你快速掌握并落地协程技术。
1. 协程到底是什么?
协程是一种轻量级的“用户级线程”,它们可以在执行过程中暂停(yield)并恢复,保持自己的执行状态。与传统线程不同,协程没有自己的堆栈;它们通过编译器生成的状态机来保存局部变量、指令指针等信息。协程使得写出顺序式的异步代码成为可能,提升代码可读性。
2. 关键概念
| 名词 | 含义 |
|---|---|
co_await |
等待一个 awaitable 对象完成,暂停协程 |
co_yield |
产生一个值,暂停协程 |
co_return |
返回协程结果,结束协程 |
promise_type |
协程的“承诺”类型,负责协程的生命周期管理 |
awaitable |
可等待的对象,满足 await_ready、await_suspend、await_resume 这三个成员函数 |
3. 标准库支持
C++20 的标准库为协程提供了 std::suspend_always、std::suspend_never、std::coroutine_handle、std::experimental::generator 等基础设施。但 std::generator 仍属于实验性(std::experimental)模块。到 C++23,实验性模块将被正式纳入标准。
4. 一个简单的协程示例:生成斐波那契数列
#include <iostream>
#include <coroutine>
#include <exception>
#include <vector>
// 简易 generator
template<typename T>
struct generator {
struct promise_type {
T current_value;
std::exception_ptr exception;
generator get_return_object() {
return generator{std::coroutine_handle <promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(T value) noexcept {
current_value = value;
return {};
}
void return_void() {}
void unhandled_exception() {
exception = std::current_exception();
}
};
std::coroutine_handle <promise_type> coro;
explicit generator(std::coroutine_handle <promise_type> h) : coro(h) {}
~generator() { if (coro) coro.destroy(); }
struct iterator {
std::coroutine_handle <promise_type> h;
bool done;
iterator(std::coroutine_handle <promise_type> h, bool done) : h(h), done(done) {}
iterator& operator++() {
h.resume();
done = h.done();
return *this;
}
const T& operator*() const { return h.promise().current_value; }
bool operator!=(const iterator& other) const { return done != other.done; }
};
iterator begin() {
if (coro) {
coro.resume();
if (coro.done()) return iterator{coro, true};
}
return iterator{coro, false};
}
iterator end() { return iterator{coro, true}; }
};
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;
}
}
int main() {
for (int x : fibonacci(10)) {
std::cout << x << ' ';
}
// 输出: 0 1 1 2 3 5 8 13 21 34
}
上述代码演示了如何使用 co_yield 产生一个可迭代的序列,并通过 generator 封装成标准容器的接口。协程内部的状态(a、b 等)在每次 co_yield 后被安全地保存在 promise_type 中。
5. 实际应用场景
| 场景 | 协程优势 |
|---|---|
| 异步 I/O | 用 co_await 代替回调链,逻辑更直观 |
| 事件驱动 | co_yield 产生事件,事件循环简单化 |
| 流式数据 | generator 以惰性方式生成数据,节省内存 |
| 协程池 | 通过手动管理 coroutine_handle 构建轻量级协程池 |
6. 性能考虑
- 堆栈开销:协程的堆栈由编译器自动分配,通常比线程小得多(数十 KB)。这使得数千协程可以同时存在,而线程数量受限于系统堆栈大小。
- 切换成本:协程切换是 CPU 指令级别的,远比线程切换快。只要协程暂停时不执行同步锁,竞争也几乎不存在。
- 内存分配:若协程内部使用大量
new,仍会产生分配开销。建议使用预分配或对象池来避免频繁分配。
7. 小结
C++20 的协程为异步编程带来了新的维度,让你在保持代码顺序式结构的同时,享受高效的并发性能。只需掌握 co_await、co_yield、co_return 的语义,并利用标准库提供的协程框架,你就能快速构建出可维护、可扩展的异步系统。欢迎在项目中大胆试验,把协程真正落地到生产环境中!