协程(Coroutines)是 C++20 标准中最具革命性的特性之一。它们让我们能够用同步的写法来处理异步逻辑,既保持了代码可读性,又不牺牲性能。本文将从基本概念、实现方式、典型使用场景以及常见坑点四个方面,带你快速上手 C++20 协程。
1. 协程基础
1.1 什么是协程?
协程是一种可以在执行期间挂起(suspend)并随后恢复的函数。与传统线程相比,协程的上下文切换开销更小,尤其适用于 I/O 等延迟操作。
1.2 核心语法
co_return:返回最终结果并结束协程。co_yield:产生一个值并挂起,后续可通过co_await继续执行。co_await:挂起当前协程,等待被等待对象完成。
1.3 协程的内部结构
协程本质上由一个 promise type 和一个 awaitable type 两部分组成。promise_type 存储协程的状态、结果以及异常;awaitable 则提供 await_ready、await_suspend 与 await_resume 三个接口,决定协程何时挂起以及如何恢复。
2. 实现一个简易的 Task 协程
下面给出一个通用的 `Task
`,表示异步返回类型为 `T` 的协程。 “`cpp #include #include #include #include #include #include template struct Task { struct promise_type; using handle_t = std::coroutine_handle ; Task(handle_t h) : coro(h) {} Task(Task&& rhs) noexcept : coro(rhs.coro) { rhs.coro = nullptr; } ~Task() { if (coro) coro.destroy(); } // 让外部等待协程完成 T get() { if (!coro.done()) coro.resume(); if (coro.promise().exc) std::rethrow_exception(coro.promise().exc); return std::move(coro.promise().value.value()); } struct promise_type { std::optional value; std::exception_ptr exc; Task get_return_object() { return Task{handle_t::from_promise(*this)}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_value(T v) { value.emplace(std::move(v)); } void unhandled_exception() { exc = std::current_exception(); } }; private: handle_t coro; }; “` **说明** – `initial_suspend` 与 `final_suspend` 均为 `suspend_always`,意味着协程从创建到结束都需要显式 `resume`。如果想让协程立即开始,可以改为 `suspend_never`。 – `get()` 会在协程尚未完成时恢复执行,并在完成后取出结果。 — ### 3. 用 `Task` 编写异步 I/O 示例 假设我们需要模拟一个网络请求,耗时 1 秒。使用协程可以让代码保持线性可读。 “`cpp Task fetch_data(int id) { std::cout **注意**:这里使用 `std::suspend_always` 仅是为了演示。实际场景中,你会让协程挂起等待某个真正的 `awaitable`(例如异步 I/O 操作)。 — ### 4. 自定义 `awaitable`:异步计时器 下面演示如何实现一个 `Timer`,支持 `co_await`。 “`cpp struct Timer { struct promise_type { std::coroutine_handle continuation; Timer get_return_object() { return Timer{std::coroutine_handle ::from_promise(*this)}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} }; Timer(std::coroutine_handle h) : handle(h) {} ~Timer() { if (handle) handle.destroy(); } std::coroutine_handle handle; }; Timer sleep_for(std::chrono::milliseconds ms) { std::thread([ms](std::coroutine_handle h){ std::this_thread::sleep_for(ms); h.resume(); // 唤醒协程 }).detach(); co_await std::suspend_always{}; } “` 使用示例: “`cpp Task demo() { std::cout ` 最终都执行 `co_return` 或 `return_value` | | **异常未捕获** | 在协程内部抛异常,未在 `promise_type` 中实现 `unhandled_exception` | 在 `promise_type` 中添加 `void unhandled_exception()` 并记录 `exc` | | **悬挂对象生命周期短** | `co_await` 的 awaitable 对象在协程挂起后已析构 | 确保 awaitable 的生命周期覆盖整个挂起期,或使用 `shared_ptr` 包装 | | **多次 `resume`** | `coro.resume()` 在 `coro.done()` 后再次调用 | 检查 `coro.done()`,或在 `get()` 前确认完成状态 | | **未使用 `std::suspend_always`** | 协程立即执行导致 `await_ready` 返回 false,导致 `await_resume` 未被调用 | 根据需要选择 `suspend_always` 或 `suspend_never` | — ### 6. 进阶话题 1. **协程与线程池**:将 `co_await` 与自定义线程池结合,可实现高并发任务调度。 2. **异步 I/O 框架**:如 libuv、asio 等已在内部使用协程,实现事件驱动模型。 3. **`generator`**:使用 `co_yield` 生成序列,适合流式数据处理。 4. **错误传播**:通过 `std::exception_ptr` 与 `try`/`catch` 在协程间传递异常。 — ### 7. 结语 C++20 协程为我们打开了一扇新的窗口:让异步代码写得像同步代码,既易读又高效。只需掌握基本的 `co_await` 语法与 `promise`/`awaitable` 的实现思路,即可在自己的项目中快速落地。希望本文能为你在协程之旅上提供一把钥匙,开启更高效、更优雅的 C++ 开发之路。祝编码愉快! —