C++20 Coroutines: A Practical Guide to Asynchronous Programming


1. 何是协程?

协程是一种轻量级的子程序,能够在执行过程中暂停和恢复。与传统的线程相比,协程不需要上下文切换的成本,只需要保存当前的栈帧信息即可。C++20 标准在 `

` 头文件中引入了协程支持,使得异步编程变得更加直观。 ## 2. 协程的基本组成 | 术语 | 含义 | |——|——| | `co_await` | 暂停协程,等待一个 awaitable 对象完成 | | `co_yield` | 暂停协程并返回一个值给调用者 | | `co_return` | 结束协程并返回最终结果 | | `std::coroutine_handle` | 对协程实例的句柄,用于控制其生命周期 | | `promise_type` | 协程的返回值包装器,定义了协程的行为 | ## 3. 一个完整的协程例子 下面的例子演示了一个生成整数序列的协程,并用 `co_await` 与自定义 awaitable 对象一起模拟异步延迟。 “`cpp #include #include #include #include #include struct AsyncSleep { std::chrono::milliseconds duration; AsyncSleep(std::chrono::milliseconds d) : duration(d) {} bool await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle h) const noexcept { std::thread([h, dur = duration]() mutable { std::this_thread::sleep_for(dur); h.resume(); }).detach(); } void await_resume() const noexcept {} }; struct Generator { struct promise_type { int current_value; Generator get_return_object() { return Generator{std::coroutine_handle ::from_promise(*this)}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void unhandled_exception() { std::terminate(); } void return_void() {} std::suspend_always yield_value(int value) { current_value = value; return {}; } }; std::coroutine_handle handle; explicit Generator(std::coroutine_handle h) : handle(h) {} ~Generator() { if (handle) handle.destroy(); } bool next() { if (!handle.done()) { handle.resume(); return !handle.done(); } return false; } int value() const { return handle.promise().current_value; } }; Generator number_sequence() { for (int i = 0; i < 5; ++i) { co_yield i; co_await AsyncSleep(std::chrono::milliseconds(500)); } } int main() { auto gen = number_sequence(); while (gen.next()) { std::cout << "Got: " << gen.value() << '\n'; } std::cout << "Finished.\n"; } “` **解释:** 1. `AsyncSleep` 是一个 awaitable 对象,在 `await_suspend` 中启动一个线程来睡眠,然后恢复协程。 2. `Generator` 是一个支持 `co_yield` 的生成器,内部使用 `promise_type` 来存储当前值。 3. `number_sequence` 生成从 0 到 4 的整数,每个值间隔 500ms。 ## 4. 协程与 `std::future` 的区别 | 特点 | `std::future` | 协程 | |——|————–|——| | 线程模型 | 需要线程或线程池 | 线程无关 | | 错误传播 | `get()` 抛异常 | `promise_type` 的 `unhandled_exception` 处理 | | 资源占用 | 线程消耗较大 | 仅栈帧和协程句柄 | | 代码可读性 | 回调/链式 `then` | 线性、顺序编写 | 协程提供了更自然的异步流程控制,而 `std::future` 更适合基于任务的并行执行。 ## 5. 常见陷阱 1. **忘记 `handle.destroy()`**:协程句柄持有资源,必须显式销毁或使用 RAII 包装。 2. **无限循环的 `co_await`**:如果 awaitable 永远不返回,协程会挂起。要么保证 `await_ready()` 最终为 `true`,要么在 `await_suspend` 里设置超时。 3. **错误传播不明确**:在 `promise_type::unhandled_exception` 中最好把异常转化为 `std::exception_ptr` 并在协程外部捕获。 4. **协程对象的拷贝**:`std::coroutine_handle` 不是拷贝安全,建议使用移动语义或指针包装。 ## 6. 高级用法 – **异步管道**:通过 `co_yield` 与 `co_await` 组合,可以实现流式数据处理,如 `async_filter`, `async_map`。 – **协程与 IO 多路复用**:在 `await_suspend` 中注册到 `io_uring` 或 `libuv` 的事件循环,让协程在 IO 完成时恢复。 – **协程协作**:多协程共享同一 `promise_type`,实现协同任务调度。 ## 7. 小结 C++20 的协程让异步编程变得更简洁、更接近同步写法。通过掌握 `co_await`、`co_yield`、`co_return` 以及 `promise_type` 的机制,你可以在项目中实现高性能的异步任务、事件驱动模型以及可组合的流式处理。随着标准库的进一步完善,协程将成为现代 C++ 开发的核心工具之一。

发表评论