在C++20中,协程(Coroutines)被正式纳入语言标准,为异步编程和生成器模式提供了语法级别的支持。它们的核心是“暂停点”(co_await、co_yield、co_return),允许函数在执行期间暂停并在后续恢复,而不需要手动维护状态机。本文将从协程的原理、实现细节、标准库支持以及实际应用场景四个方面进行系统阐述,并通过代码示例展示如何在项目中落地。
1. 协程的基本概念
- 协程:一种轻量级的执行单元,能够在运行期间暂停并恢复。
- Suspend Point:协程中的暂停点,使用
co_await(等待异步结果)、co_yield(生成值)或co_return(结束并返回结果)。 - Coroutine Handle:
std::coroutine_handle用于操纵协程(resume、destroy、获取状态)。
协程不是线程,而是基于同一线程执行的非抢占式任务。它们通过生成的状态机来保存本地变量和执行位置。
2. 协程实现的幕后细节
2.1 生成器状态机
编译器将协程函数转换为类(生成器),该类包含:
promise_type:用于保存协程结果、异常、悬挂点等。await_suspend/await_resume:定义等待行为。initial_suspend/final_suspend:决定协程是否在创建时立即挂起,以及结束时是否挂起。
struct MyGenerator {
struct promise_type {
int value;
MyGenerator get_return_object() { return MyGenerator{std::coroutine_handle <promise_type>::from_promise(*this)}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::rethrow_exception(std::current_exception()); }
void return_void() {}
std::suspend_always yield_value(int v) { value = v; return {}; }
};
std::coroutine_handle <promise_type> handle;
int next() { handle.resume(); return handle.promise().value; }
bool done() const { return !handle || handle.done(); }
};
2.2 内存与栈管理
协程不需要完整栈拷贝;局部变量被保存在堆中的 promise 对象中。由于所有状态均在堆上,协程可以在任意时间暂停,而不必依赖调用栈的完整性。
3. C++20 标准库对协程的支持
3.1 std::generator(实验性)
在 C++23 之后,`std::generator
` 作为标准容器提供,用来简化生成器编写。它内部封装了 `promise_type` 与 `co_yield` 的细节。 “`cpp #include std::generator count_to(int n) { for (int i = 0; i < n; ++i) co_yield i; } “` ### 3.2 `std::future` 与 `std::async` 的协程化 C++20 引入了 `co_await std::future `,允许直接在协程中等待 `std::future` 的结果,简化异步链式调用。 “`cpp #include std::future fetch_data(); // 远程获取 std::future process() { int data = co_await fetch_data(); // 等待 co_return data * 2; } “` ### 3.3 `std::ranges` 与协程 `std::ranges::views::iota` 与 `std::ranges::views::generate` 结合协程可实现惰性生成。协程可作为自定义视图的源。 ## 4. 实际应用案例 ### 4.1 异步 I/O(网络编程) 在网络框架(如 `cpp-httplib`、`Boost.Asio`)中,协程可以用来处理异步 I/O,消除回调地狱。 “`cpp #include #include asio::awaitable handle_client(asio::ip::tcp::socket sock) { char buf[1024]; std::size_t n = co_await sock.async_read_some(asio::buffer(buf), asio::use_awaitable); co_await sock.async_write_some(asio::buffer(buf, n), asio::use_awaitable); } “` ### 4.2 生成器(数据流、懒加载) 协程生成器非常适合实现惰性求值,如读取大文件时按行产生: “`cpp std::generator read_lines(std::istream& in) { std::string line; while (std::getline(in, line)) co_yield line; } “` ### 4.3 任务调度器(协程池) 通过 `std::coroutine_handle` 可以实现一个协程池,按需调度协程任务: “`cpp class CoroutinePool { public: void add(std::function<std::future()> job) { jobs.emplace_back(std::move(job)); } void run() { while (!jobs.empty()) { auto f = jobs.front()(); f.wait(); // 或 co_await jobs.pop_front(); } } private: std::deque<std::function<std::future()>> jobs; }; “` ## 5. 性能与可维护性考量 – **避免无限循环**:协程会在每次 `co_yield` 产生时保存状态,频繁 yield 可能导致堆分配成本。 – **异常传播**:`promise_type` 的 `unhandled_exception` 自动捕获异常,可通过 `try-catch` 捕获。 – **调试工具**:GDB、LLDB 支持 `co_yield`,但调试时需注意堆上状态。 ## 6. 未来展望 C++23 与 C++26 计划继续扩展协程功能: – **`std::coroutine_traits`** 的自定义化支持。 – **`std::generator`** 的官方标准化。 – **协程的异常处理** 更加细粒度的控制。 总之,C++20 协程为现代 C++ 提供了更自然、更高效的异步编程模型。掌握其原理与标准库的使用,可以让开发者在不牺牲性能的前提下,写出更清晰、可维护的异步代码。</std::function<std::future</std::future