在 C++20 标准中,协程(coroutine)被正式纳入标准库,为异步编程提供了强大的工具。相比传统的回调、线程或事件循环,协程可以让代码保持同步写法,同时在执行过程中暂停和恢复,从而实现高效、可读性更好的异步代码。本文将从实现原理、关键类、典型使用场景以及性能注意事项等方面,系统阐述 C++20 协程的核心概念与实践。
1. 协程的基本概念
1.1 何为协程?
协程是一种轻量级的执行单元,允许在函数内部随时挂起(co_await、co_yield、co_return)并在需要时恢复执行。不同于线程,协程在同一线程中切换,其上下文切换成本极低。
1.2 协程的生命周期
| 步骤 | 说明 |
|---|---|
| ① 构造 | 调用协程函数时会生成一个 promise 对象与 handle(std::coroutine_handle)。 |
| ② 挂起 | 第一次遇到 co_await 或 co_yield 时会挂起,返回 handle 的 resume() 可以继续。 |
| ③ 恢复 | resume() 调用后,协程从挂起点继续执行。 |
| ④ 完成 | 执行到 co_return 或抛出异常,协程结束,handle 变为空。 |
2. 关键类型与宏
| 类型 | 作用 | 典型使用方式 |
|---|---|---|
| `std::coroutine_handle | ||
| 表示协程句柄,负责挂起/恢复 |auto h = handle.resume();` |
||
std::suspend_always / std::suspend_never |
控制协程是否挂起 | co_await std::suspend_always{}; |
| `std::generator | ||
(实验性) | 用于co_yield的生成器 |for (auto x : gen) {}` |
||
std::async + std::future |
传统异步工具 | `std::future |
| f = std::async([]{…});` |
3. 协程的实现细节
3.1 Promise 与 Awaiter
- Promise:协程函数返回的隐藏类型,负责在协程创建时存储返回值、异常以及协程状态。
- Awaiter:实现
await_ready()、await_suspend()、await_resume()接口,决定协程是否挂起以及挂起时的行为。
示例 Awaiter 结构:
template<typename T>
struct Awaiter {
T value;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) const noexcept { /* 异步任务开始 */ }
T await_resume() const noexcept { return value; }
};
3.2 协程框架的调用链
main() -> corofunc()
-> Promise() -> handle
-> co_await Awaiter
-> suspend -> return to main
... resume ...
4. 典型使用场景
4.1 网络 I/O
使用 asio 或自定义事件循环,结合协程可以将异步读写写成同步代码:
async_tcp_client::read_line() -> std::string {
std::vector <char> buffer(1024);
std::size_t n = co_await async_read(socket, buffer);
std::string line(buffer.begin(), buffer.begin() + n);
co_return line;
}
4.2 并行任务调度
借助协程与协程池,可以在单线程内并行执行多任务,避免线程上下文切换成本。
generator <int> worker(int id) {
for (int i = 0; i < 10; ++i) {
co_yield id * i;
co_await std::suspend_always{};
}
}
4.3 流式数据处理
使用 `std::generator
` 可对大数据流进行惰性求值,降低内存占用。 “`cpp std::generator primes() { int n = 2; while (true) { if (is_prime(n)) co_yield n; ++n; } } “` ## 5. 性能注意事项 1. **避免过度挂起**:频繁 `co_await` 可能导致性能损失,尤其是同步等待时。尽量将长时间等待放在单独任务中。 2. **对象大小**:协程对象需要存储 promise、awaiter 等,过大会导致栈溢出。使用 `co_yield` 时,generator 的 `value_type` 需考虑内存对齐。 3. **异常安全**:异常会在协程结束时抛出,务必在 promise 中正确处理 `unhandled_exception()`。 4. **调试支持**:IDE 对协程的调试支持仍有限,建议使用 `-fcoroutines` 并开启 `-fno-inline` 以便追踪。 ## 6. 代码示例:异步文件读取 “`cpp #include #include #include #include struct async_read_result { std::string data; struct awaiter { async_read_result* self; bool await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle h) const noexcept { // 异步读取文件 std::thread([self, h]{ std::ifstream file(“big.txt”); self->data.assign((std::istreambuf_iterator (file)), std::istreambuf_iterator ()); h.resume(); }).detach(); } async_read_result await_resume() const noexcept { return *self; } }; auto operator co_await() const noexcept { return awaiter{const_cast(this)}; } }; async_read_result read_file_async() { async_read_result res; co_return res; } int main() { auto task = read_file_async(); std::string content = co_await task; // 只在协程上下文中使用 std::cout 说明:上述示例使用 `co_await` 对异步读取进行封装,真正的 I/O 任务在后台线程完成,主线程可以继续执行其他操作。 ## 7. 结语 C++20 协程为 C++ 开发者提供了一套统一、轻量且高效的异步编程工具。通过熟练掌握 promise/awaiter 的机制、正确使用 `std::suspend_always` 等暂停策略以及合理设计任务调度,程序员可以在保持代码同步可读性的同时,实现高性能的并发与异步逻辑。随着标准库的进一步完善,协程将在未来的 C++ 生态中扮演更为重要的角色。