C++20 中引入了协程(coroutine)机制,为异步编程提供了更直观、更高效的语法和实现方式。相比传统的回调、线程或基于事件循环的实现,协程在编译期生成状态机,能够让程序员用同步代码的写法表达异步逻辑,既保持了代码的可读性,也大幅降低了运行时的开销。下面从语法、使用场景、实现细节以及实际代码示例等几个方面,系统梳理 C++20 协程的核心概念与使用方法。
1. 协程的基本语法
1.1 co_await
co_await 关键字用于挂起协程,并等待一个 Awaitable 对象完成。Awaitable 对象需要满足以下三点:
await_ready()返回bool,表示是否立即完成。await_suspend()接受std::coroutine_handle<>并返回bool(是否挂起)。await_resume()返回结果。
1.2 co_yield
co_yield 用于生成器模式,返回值给调用方,随后协程挂起,等待下一次 next() 调用。
1.3 co_return
co_return 用于结束协程,返回最终结果。
1.4 协程函数的返回类型
协程函数的返回类型通常是自定义的 `Task
` 或 `Generator`,它们内部持有 `promise_type`。`promise_type` 用于定义协程的生命周期、异常处理以及与 Awaitable 交互的逻辑。 ## 2. 实用示例:异步文件读取 “`cpp #include #include #include #include #include struct AsyncFileReader { struct promise_type; using handle_t = std::coroutine_handle ; handle_t coro; AsyncFileReader(handle_t h) : coro(h) {} ~AsyncFileReader() { if (coro) coro.destroy(); } struct promise_type { std::vector buffer; std::ifstream file; std::string filename; std::exception_ptr eptr; AsyncFileReader get_return_object() { return AsyncFileReader{handle_t::from_promise(*this)}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void unhandled_exception() { eptr = std::current_exception(); } std::suspend_always await_ready() { return {}; } std::suspend_always await_suspend(handle_t h) { // 非阻塞读取模拟(这里简化为同步打开文件) file.open(filename, std::ios::binary); return {}; } std::size_t await_resume() { return file ? file.tellg() : 0; } void return_void() {} }; }; AsyncFileReader readFileAsync(const std::string& name) { co_await std::suspend_always{}; // 触发协程 std::cout 说明:上述示例仅为演示协程结构的最小化实现,真实项目中应结合 I/O 库(如 `boost::asio` 或 `libuv`)实现真正的异步 I/O。 ## 3. 协程实现细节 ### 3.1 状态机生成 编译器在编译协程函数时,会把函数体拆分为若干“挂起点”,并为每个挂起点生成一个状态。协程的执行器(`coroutine_handle`)保存当前状态,`resume()` 调用时会跳转到对应的状态,执行到下一个挂起点或结束。 ### 3.2 Promise 对象 `promise_type` 在协程创建时分配,负责: – **返回值**:通过 `get_return_object()` 返回协程包装器。 – **异常**:`unhandled_exception()` 捕获未处理异常,存储到 `exception_ptr`,供调用方 `resume()` 时抛出。 – **挂起/恢复**:`initial_suspend()`、`final_suspend()` 控制协程启动/结束时是否挂起。 – **Awaitable 交互**:实现 `await_ready()`、`await_suspend()`、`await_resume()`。 ### 3.3 内存布局 协程体在堆上分配(使用 `operator new` 或 `std::allocator`)。状态机数据(局部变量)被包装在 `promise_type` 的成员中,或者通过 `suspend_always` 等机制移动到堆上。这样做避免了栈帧回弹导致的递归栈溢出,同时降低了栈空间占用。 ### 3.4 异常安全 协程在任何挂起点都可能抛出异常。实现时需确保: – `destroy()` 被安全调用,即使协程被异常终止。 – `unhandled_exception()` 将异常捕获并保存,后续 `resume()` 时重新抛出。 ## 4. 与传统异步方式的对比 | 特性 | 回调 | Promise/Future | async/await (C++20) | |——|——|—————-|———————| | 代码可读性 | 差 | 适中 | 高 | | 资源占用 | 线程或事件循环 | 单线程 + 任务队列 | 线程轻量 + 状态机 | | 并发模型 | 线程/事件 | 线程池 | 协程轻量 | | 调试难度 | 高 | 中 | 低 | C++20 协程通过将挂起点与同步代码的结构保持一致,显著提升了可维护性和可读性,已成为现代 C++ 并发编程的核心工具。 ## 5. 进一步学习资源 1. 《C++ Concurrency in Action》第二版 2. 官方 C++20 标准草案([n4861](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/n4861.pdf)) 3. Boost.Coroutine2 与 cppcoro 库的实战案例 — > **总结** > 协程的核心优势在于: > 1. **直观的同步写法**:让异步逻辑像普通循环一样自然。 > 2. **低资源占用**:协程本质上是状态机,而非线程,几乎不需要额外栈空间。 > 3. **高效的上下文切换**:挂起与恢复是编译期生成的轻量跳转。 掌握协程的基本语法与实现原理后,你可以在网络 IO、GUI 事件驱动、游戏循环等多种场景中轻松应用,提升代码质量与运行效率。