**C++20中的协程:从概念到实践**

协程是C++20为实现异步编程、简化状态机和延迟计算等场景所引入的一种语言级别特性。它们允许函数暂停执行、恢复并携带局部状态,极大地提升了代码的可读性和可维护性。本文将从协程的基本概念、实现细节,到一个完整的异步任务调度器示例进行讲解,帮助读者快速掌握协程的核心用法。


1. 协程基础

协程的核心是 co_awaitco_yieldco_return 三个关键字。

关键字 作用
co_await 暂停协程并等待一个 Awaitable 对象完成。
co_yield 暂停协程并返回一个值给调用者。
co_return 结束协程并返回一个值。

协程的入口函数需要返回一个 *`std::experimental::generator

`*、*`std::experimental::task`* 或自定义 Awaitable 类型。C++20 标准库已包含了 *`std::generator`*(在 “)和 *`std::task`*(在 “)的基础实现。 — ### 2. Awaitable 规范 一个类型要成为协程可等待对象,必须满足以下接口: “`cpp struct MyAwaitable { bool await_ready(); // 若返回 true 则立即继续执行 void await_suspend(std::coroutine_handle h); // 若返回 true,协程挂起 T await_resume(); // 返回值 }; “` – **`await_ready()`**:决定是否需要挂起。若为 `true`,协程会立即继续执行,`await_resume()` 被直接调用。 – **`await_suspend()`**:挂起时被调用,可用来将协程句柄存入事件循环或线程池。若返回 `false`,协程会立即恢复。 – **`await_resume()`**:当协程恢复时被调用,返回最终结果。 — ### 3. 典型协程示例 下面给出一个简单的 **异步计数器** 示例,展示协程如何与事件循环结合。 “`cpp #include #include #include #include #include #include #include using namespace std::chrono_literals; // 简单事件循环 struct EventLoop { std::queue<std::function> tasks; void run() { while (!tasks.empty()) { auto task = std::move(tasks.front()); tasks.pop(); task(); } } void schedule(std::function fn) { tasks.push(std::move(fn)); } }; // Awaitable: 延迟一段时间 struct Sleep { std::chrono::milliseconds duration; EventLoop& loop; std::coroutine_handle handle; bool await_ready() const noexcept { return duration.count() == 0; } void await_suspend(std::coroutine_handle h) noexcept { handle = h; // 通过线程模拟超时,真实项目中会用异步 I/O std::thread([this]{ std::this_thread::sleep_for(duration); loop.schedule([this]{ handle.resume(); }); }).detach(); } void await_resume() const noexcept {} }; // 协程函数:打印 0~5,间隔 1 秒 auto counter(EventLoop& loop) -> std::experimental::generator { for (int i = 0; i < 6; ++i) { co_yield i; // 暂停并返回当前值 co_await Sleep{1s, loop}; // 等待 1 秒 } } int main() { EventLoop loop; auto gen = counter(loop); // 将协程的所有操作放入事件循环 for (auto val : gen) { loop.schedule([val](){ std::cout << "count: " << val << '\n'; }); } loop.run(); // 立即执行已调度的任务 // 主线程等待事件循环完成 std::this_thread::sleep_for(7s); } “` **说明** – `Sleep` 通过 `std::thread` 模拟延迟,真正的实现通常会依赖 I/O 完成事件(epoll、IOCP 等)。 – `co_yield` 产生的值通过 `generator` 供外部迭代。 – `EventLoop` 将所有协程产生的任务排队执行,避免线程同步的复杂性。 — ### 4. 高级用法:异步 I/O 与协程 在实际项目中,协程往往与**网络 I/O**、**磁盘 I/O**、**数据库查询** 等异步操作结合。下面以 `boost::asio` 为例,演示如何用协程等待异步连接。 “`cpp #include #include #include using boost::asio::ip::tcp; using namespace boost::asio::experimental::awaitable_operators; awaitable do_echo(tcp::socket socket) { try { char data[1024]; for (;;) { std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data), use_awaitable); co_await async_write(socket, boost::asio::buffer(data, n), use_awaitable); } } catch (std::exception& e) { std::cerr << "Session error: " << e.what() << '\n'; } } awaitable server(boost::asio::io_context& ioc, unsigned short port) { tcp::acceptor acceptor(ioc, tcp::endpoint(tcp::v4(), port)); for (;;) { tcp::socket socket = co_await acceptor.async_accept(use_awaitable); co_spawn(ioc, do_echo(std::move(socket)), detached); } } int main() { boost::asio::io_context ioc; co_spawn(ioc, server(ioc, 12345), detached); ioc.run(); } “` – `awaitable ` 是 Boost.Asio 对协程的封装。 – `use_awaitable` 让 I/O 操作返回一个 Awaitable,便于在协程中 `co_await`。 — ### 5. 常见陷阱与最佳实践 | 场景 | 常见问题 | 解决方案 | |——|———-|———-| | **递归协程** | 递归深度导致栈溢出 | 使用迭代实现或显式手动管理协程句柄 | | **异常传播** | 异常被吞噬 | 在协程入口捕获异常并通过 `co_return` 传递给调用方 | | **线程安全** | 多线程访问同一协程 | 使用 `std::mutex` 或在事件循环内序列化执行 | | **资源泄漏** | 协程句柄未恢复 | 在 `await_suspend` 内确保正确调用 `handle.resume()` | — ### 6. 结语 协程为 C++ 程序员提供了更直观、更高效的异步编程方式。掌握其基本语法、Awaitable 约定以及与事件循环、异步 I/O 的配合,是实现现代高性能服务器、游戏引擎、实时数据处理等场景的关键。随着 C++23 对协程的进一步完善(如 `std::generator`、`std::task` 等更完整的实现),协程将成为 C++ 生态中不可或缺的一部分。希望本文能帮助你在项目中快速上手并发挥协程的强大能力。</std::function

发表评论