协程(coroutine)是 C++20 标准库新增的一项强大特性,旨在为编程者提供一种更直观、更高效的方式来编写异步、并发或生成式代码。与传统的回调、Future/Promise 以及线程池等技术相比,协程在语法简洁、错误可控、资源占用更低等方面具有显著优势。本文将从协程的核心概念、实现细节以及实际应用场景等角度进行深入剖析,并给出一段完整的示例代码,帮助读者快速上手。
1. 协程的核心概念
1.1 什么是协程?
协程是一种“轻量级线程”,可以在执行过程中挂起(suspend)并在稍后恢复(resume)。不同于线程在切换时需要保存完整的栈信息,协程只需要保存调用栈中的局部状态、控制权和参数,使得协程上下文切换的开销极低。
1.2 关键术语
- generator:一种特殊的协程,用于产生序列(如
std::generator)。每一次co_yield都会产生一个值并挂起。 - task:另一种协程,代表一个可等待的异步操作,通常返回
std::future或std::shared_future。使用co_return结束。 - awaiter:实现
await_suspend,await_resume的对象,用于描述挂起与恢复的行为。
1.3 标准库支持
C++20 引入了 `
` 头文件,并提供了以下基础组件: – `std::suspend_always` / `std::suspend_never` – `std::generator`(实验性,需启用宏 `__cpp_lib_coroutine`) – `std::experimental::generator`(在 C++20 之前的实验实现) – `std::future` 与 `std::async` 的协程适配器(如 `std::future` 的 `awaitable`) — ## 2. 协程的实现原理 ### 2.1 控制流转换 当编译器遇到 `co_await`, `co_yield`, `co_return` 等关键词时,会将函数拆分成若干“段”并生成一个状态机。该状态机的核心是 `promise_type`,负责维护协程的状态、返回值和异常。 ### 2.2 `promise_type` 的角色 – **`get_return_object`**:返回协程的句柄(如 `std::generator` 对象)。 – **`initial_suspend` / `final_suspend`**:决定协程在开始和结束时是否挂起。 – **`return_value` / `unhandled_exception`**:处理返回值和异常。 – **`yield_value`**:在 `co_yield` 时使用,产生值并挂起。 ### 2.3 内存管理 协程的堆栈由编译器自动分配(通常在堆上),不需要手动管理。若使用 `co_await`,其对应的 `awaiter` 对象负责在协程挂起后恢复执行。 — ## 3. 示例:使用协程实现一个异步数据读取器 以下代码演示了如何使用 C++20 协程读取文件,并在读取完成后返回一个 `std::generator`,每次 `co_yield` 返回一行文本。 “`cpp #include #include #include #include #include // 简易的 generator 类型(C++20 实验性支持) template class generator { public: struct promise_type { T current_value; std::suspend_always yield_value(T value) { current_value = std::move(value); return {}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } generator get_return_object() { return generator{ std::coroutine_handle ::from_promise(*this) }; } void return_void() {} void unhandled_exception() { std::terminate(); } }; using handle_type = std::coroutine_handle ; explicit generator(handle_type h) : coro(h) {} generator(const generator&) = delete; generator& operator=(const generator&) = delete; generator(generator&& other) noexcept : coro(other.coro) { other.coro = nullptr; } ~generator() { if (coro) coro.destroy(); } class iterator { public: iterator(handle_type h) : coro(h) {} iterator& operator++() { coro.resume(); if (!coro.done()) return *this; else coro = nullptr; } bool operator==(std::default_sentinel_t) const { return !coro || coro.done(); } const T& operator*() const { return coro.promise().current_value; } private: handle_type coro; }; iterator begin() { if (coro) coro.resume(); return iterator(coro); } std::default_sentinel_t end() { return {}; } private: handle_type coro; }; // 异步文件读取器 generator async_readlines(const std::string& filename) { std::ifstream fin(filename); if (!fin.is_open()) { throw std::runtime_error(“Cannot open file”); } std::string line; while (std::getline(fin, line)) { // 在这里可以模拟 I/O 阻塞后挂起 co_yield line; } } “` **使用示例** “`cpp int main() { try { for (const auto& line : async_readlines(“example.txt”)) { std::cout async_readlines(…)`。 2. **`co_yield` 的使用**:每读取一行文本即挂起并返回该行。 3. **异常处理**:通过 `unhandled_exception` 在协程内部抛出异常,主程序捕获。 — ## 4. 协程的优势与限制 ### 4.1 优势 – **低上下文切换成本**:只保存局部状态,避免线程栈复制。 – **直观的异步语义**:代码写法接近同步,易于阅读与维护。 – **资源控制**:协程句柄可以自动销毁,减少手动资源管理。 ### 4.2 限制 – **编译器支持**:C++20 仍处于草案阶段,部分编译器(如 MSVC、GCC、Clang)在实现细节上可能不完全兼容。 – **调试困难**:协程的状态机隐藏了函数调用堆栈,调试时需要特殊工具。 – **生命周期管理**:协程句柄必须在所有协程完成前保持有效,错误管理不当可能导致悬空指针。 — ## 5. 进阶话题 1. **协程与异步 IO**:与 `asio`、`libuv` 等库结合,实现高性能网络编程。 2. **任务调度器**:自定义 `awaiter`,实现优先级调度或工作池。 3. **协程与 `std::thread` 的混用**:在多核环境下,协程可与线程池配合使用,既保持轻量级协程的优势,又能充分利用多核资源。 — ## 6. 结语 C++20 协程为开发者提供了一种更自然、更高效的异步编程模型。通过上述示例,读者已经掌握了协程的基本语法与使用方式。建议后续进一步探索协程在网络编程、游戏循环以及大型数据处理中的应用,以充分挖掘 C++20 带来的潜力。祝编码愉快!