C++20 引入了协程(Coroutines),为异步编程提供了语言层面的支持。相比传统的回调、Promise 或线程,协程能够以同步的写法表达异步流程,提升可读性与可维护性。本文将从实现原理、关键语法、编译器优化以及实际应用四个方面,剖析 C++20 协程的核心机制。
1. 协程的核心概念
1.1 协程函数
协程函数是使用 co_await, co_yield, co_return 等关键字定义的特殊函数。其返回类型必须满足 协程返回类型(co-routine return type)要求,例如 `std::future
`, `generator`, `task` 等。
### 1.2 协程状态机
协程函数在调用时并不会立即执行,而是返回一个 *悬挂句柄*(`std::coroutine_handle`)。在内部,编译器会将协程函数转换为状态机,记录执行点和局部变量的状态。每一次 `co_await` 或 `co_yield` 会导致协程挂起,随后在满足条件时恢复执行。
### 1.3 关键字功能
– `co_await expr`: 等待 `expr` 所表示的 awaitable 对象完成。协程挂起,控制权交还给调用者。
– `co_yield expr`: 将 `expr` 作为生成器的下一个值返回,随后挂起。
– `co_return expr`: 结束协程,返回最终结果。
—
## 2. 协程的实现细节
### 2.1 Awaitable 约定
一个对象要能被 `co_await`,它必须满足 Awaitable 约定:
“`cpp
struct MyAwaitable {
bool await_ready() const; // 是否已完成
void await_suspend(std::coroutine_handle h);
auto await_resume() const;
};
“`
– `await_ready()`:若返回 `true`,协程继续执行。
– `await_suspend()`:协程挂起时调用。通常将协程句柄保存在异步操作完成时唤醒的回调中。
– `await_resume()`:协程恢复后获取结果。
### 2.2 生成器(Generator)
C++20 标准库未直接提供 `generator
`,但 Boost 和自定义实现常用如下模式:
“`cpp
template
struct generator {
struct promise_type {
T current_value;
std::suspend_always yield_value(T value) {
current_value = value;
return {};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
generator get_return_object() {
return generator{std::coroutine_handle
::from_promise(*this)};
}
void return_void() {}
void unhandled_exception() {}
};
std::coroutine_handle
handle;
// iterator implementation omitted
};
“`
此模式使 `co_yield` 能产生一个可迭代的序列。
### 2.3 异步任务(Task)
类似于 `std::future
`,但支持 `co_await`:
“`cpp
template
struct task {
struct promise_type {
std::promise
prom;
task get_return_object() {
return task{std::coroutine_handle
::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(T value) { prom.set_value(value); }
void unhandled_exception() { prom.set_exception(std::current_exception()); }
};
std::future
future() { return handle.promise().prom.get_future(); }
std::coroutine_handle
handle;
};
“`
这样 `co_await task
` 与 `await` 的使用者一样,能得到 `int`。
—
## 3. 编译器层面的优化
C++20 协程被编译为状态机时,编译器可以:
– **逃逸分析**:若协程在调用者栈帧中不被持久化,可直接在栈上分配。
– **循环展开**:将小型协程展开为普通函数,消除上下文切换。
– **即时唤醒**:若 awaitable 立即就绪,编译器可在同一帧内完成恢复,避免挂起。
这些优化大幅降低了协程的运行时开销,使其几乎与同步代码相当。
—
## 4. 实际应用场景
### 4.1 网络 I/O
使用 `co_await` 与异步 I/O 框架(如 ASIO、libuv)的 awaitable 对象结合,编写可读性极佳的网络服务器:
“`cpp
task
handle_connection(asio::ip::tcp::socket sock) {
std::vector
buf(1024);
std::size_t n = co_await async_read(sock, asio::buffer(buf));
co_await async_write(sock, asio::buffer(buf, n));
}
“`
### 4.2 事件循环
自定义事件循环,将 `task` 与 `asio::io_context` 绑定,实现单线程多任务调度。
### 4.3 并行计算
在多核 CPU 上,将长循环拆分为若干 `co_yield` 产生子任务,使用 `std::thread` 或 `std::async` 并行执行。
—
## 5. 典型错误与调试技巧
– **忘记 `await_ready()`**:若未实现,协程会无条件挂起,导致死锁。
– **异常传播**:`await_suspend` 内抛异常会导致协程异常终止,务必捕获并处理。
– **句柄失效**:`co_return` 前若返回值为引用,需确保引用对象生命周期足够长。
调试协程可以使用 GDB 的 `info coroutine`,或者在 `await_resume()` 内插入日志。
—
## 6. 未来展望
– **标准库统一**:预计在 C++23/24 中会加入 `std::generator`、`std::task` 等标准实现。
– **更丰富的 Awaitable**:与协程结合的 `std::sync::future`、`std::channel` 等类型将成为标准工具。
– **跨平台异步框架**:协程将成为异步框架(如 libuv、Boost.Asio)的核心模型,进一步简化异步代码。
—
### 小结
C++20 协程为 C++ 带来了强大的异步编程能力。通过 `co_await`, `co_yield` 等关键字,以及 awaitable 约定,开发者可以在保持同步语义的同时,享受异步执行的性能与灵活性。理解协程的实现原理与实际应用,可帮助你写出更简洁、可维护且高效的 C++ 代码。