在 C++20 中,协程(coroutines)正式成为语言的一部分,为异步编程和生成器提供了强大而灵活的工具。本文将从基本概念、实现细节、典型场景以及常见陷阱四个角度,帮助你快速掌握协程的实用技巧,并在实际项目中避免常见误区。
1. 协程的基本组成
协程函数与普通函数的差别在于它可以挂起(co_await、co_yield、co_return)并在随后恢复执行。其核心关键字包括:
| 关键字 | 作用 |
|---|---|
co_await |
等待一个 awaitable 对象,挂起协程 |
co_yield |
生成一个值并挂起,类似生成器 |
co_return |
结束协程并返回值,若没有值则相当于 void 返回 |
co_await 前的 awaitable 对象 |
必须满足 await_ready、await_suspend、await_resume 接口 |
协程的执行上下文(栈帧)由 Promise 对象持有,编译器会自动生成 awaiter 结构体。
2. 协程的实现细节
2.1 Promise 与 awaitable
struct AsyncTask {
struct promise_type {
int result_;
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(int v) { result_ = v; }
AsyncTask get_return_object() {
return AsyncTask{std::coroutine_handle <promise_type>::from_promise(*this)};
}
void unhandled_exception() { std::rethrow_exception(std::current_exception()); }
};
std::coroutine_handle <promise_type> coro_;
explicit AsyncTask(std::coroutine_handle <promise_type> h) : coro_(h) {}
~AsyncTask() { if (coro_) coro_.destroy(); }
int get() { return coro_.promise().result_; }
};
initial_suspend()控制协程在入口处是否挂起,final_suspend()控制结束后挂起时间点。return_value存储返回结果,get_return_object把 promise 转成用户可见的对象。
2.2 awaitable 的实现
struct TimerAwaitable {
std::chrono::milliseconds wait_for;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
// 在异步事件循环中注册定时器
event_loop::schedule(wait_for, [h](){ h.resume(); });
}
void await_resume() noexcept {}
};
await_ready 负责判断是否立即完成;await_suspend 负责挂起协程并安排恢复;await_resume 负责恢复时返回值。
3. 典型应用场景
3.1 异步 I/O
AsyncTask read_file(const std::string& path) {
auto file = co_await async_open(path, O_RDONLY);
char buffer[1024];
while (true) {
std::size_t n = co_await async_read(file, buffer, sizeof(buffer));
if (n == 0) break;
process(buffer, n);
}
co_return 0;
}
async_open、async_read 均返回 awaitable,协程在 I/O 完成前挂起,避免阻塞线程。
3.2 生成器
Generator <int> fibonacci(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
int t = a + b;
a = b;
b = t;
}
}
`Generator
` 需要实现 `operator++` 与 `operator*`,协程内部使用 `co_yield` 产生每个值。 #### 3.3 事件循环与调度器 协程与事件循环(如 `asio::io_context`)配合,能将回调式代码转化为线性可读代码: “`cpp AsyncTask main() { std::string data = co_await fetch_from_network(“http://example.com”); std::cout