C++20协程:实现异步任务的全新方式

协程是 C++20 标准引入的一项强大功能,它让我们可以用同步代码的写法来处理异步流程。相比传统的回调或线程池,协程在性能、可读性和错误处理方面都有显著优势。下面,我们从基础概念、关键字、实现细节和实际使用场景四个方面,对 C++20 协程进行全面拆解,帮助你快速上手。


1. 协程基础概念

概念 解释
协程函数 co_awaitco_yieldco_return 的普通 C++ 函数。
Suspension Point 代码执行被挂起的位置,常见的有 co_awaitco_yield
Awaitable 可以被 co_await 的对象。实现了 await_ready()await_suspend()await_resume() 三个成员函数。
Promise 协程的上下文,用于保存返回值、异常等。
Coroutine Handle 对协程的句柄,能够启动、挂起、销毁协程。

协程的执行流程:

  1. 创建:编译器为协程生成一个状态机结构,自动创建 promise_type
  2. 开始:调用 handle.resume() 或者在协程入口自动调用。
  3. 挂起:遇到 co_awaitco_yield,执行 await_suspend(),返回 true 则挂起,否则直接继续。
  4. 恢复:调用 handle.resume(),继续执行到下一个挂起点。
  5. 结束:执行到 co_return 或者抛出异常,await_resume() 被调用完成整个协程。

2. 关键字与核心 API

关键字 用途
co_await 等待一个 Awaitable 对象的完成。
co_yield 在生成器函数中产生一个值,返回给调用者。
co_return 结束协程并返回一个值(若存在)。

2.1 co_await 细节

co_await 需要待等待对象满足 Awaitable 接口:

struct MyAwaitable {
    bool await_ready() noexcept { return false; }
    void await_suspend(std::coroutine_handle<>) noexcept;
    int await_resume() noexcept { return 42; }
};
  • await_ready():如果返回 true,协程立即继续执行,不会挂起。
  • await_suspend(handle):挂起点,决定协程是否真正挂起。可以在此把协程 handle 存到某个事件系统中。
  • await_resume():协程恢复后,co_await 表达式的结果。

2.2 co_yield 与生成器

生成器函数返回 `std::generator

`(需要 “ 或第三方实现)。示例: “`cpp std::generator count(int n) { for (int i = 0; i #include #include #include #include struct FileReadAwaitable { asio::ip::tcp::socket& socket_; std::vector buffer_; std::size_t bytes_read_ = 0; bool await_ready() noexcept { return false; } void await_suspend(std::coroutine_handle h) noexcept { socket_.async_read_some( asio::buffer(buffer_), [h](std::error_code ec, std::size_t n){ // 这里可以捕获错误 h.resume(); }); } std::size_t await_resume() noexcept { return bytes_read_; } }; struct AwaitableFileReader { asio::ip::tcp::socket socket_; std::vector buffer_{1024}; AwaitableFileReader(asio::io_context& ctx, const std::string& host, const std::string& port) : socket_(ctx) { // 简化示例:直接连接 asio::ip::tcp::resolver resolver(ctx); auto endpoints = resolver.resolve(host, port); asio::connect(socket_, endpoints); } std::size_t operator()(std::vector & out) { FileReadAwaitable awaitable{socket_, buffer_}; std::size_t n = co_await awaitable; out.assign(buffer_.begin(), buffer_.begin() + n); co_return n; } }; “` 使用方式: “`cpp asio::io_context ctx; AwaitableFileReader reader(ctx, “example.com”, “80”); std::vector data; auto task = reader(data); ctx.run(); “` — ## 4. 协程的性能优势 | 传统异步方案 | 协程方案 | |————–|———-| | 回调层层嵌套 | 线性代码 | | 线程上下文切换 | 无上下文切换 | | 难以捕获异常 | `try/catch` 直接作用于协程体 | | 资源管理复杂 | `co_return` 与 RAII 结合简洁 | 实验表明,使用协程进行网络 I/O 或文件 I/O 时,吞吐量提升 20%~30%,CPU 使用率下降 10%~15%。 — ## 5. 注意事项与常见坑 1. **Awaitable 必须满足标准**:忘记实现 `await_suspend` 会导致编译错误。 2. **协程对象生命周期**:不要在协程内部持有局部变量的引用,导致悬空引用。 3. **异常传递**:异常会直接抛到协程的调用者,需要在调用点使用 `try/catch`。 4. **内存占用**:每个协程都要存一个状态机,若并发数极高,需使用 `std::async` 或线程池做分流。 5. **与第三方库兼容**:部分库(如 Boost.Asio)已有协程包装,直接使用即可。 — ## 6. 结语 C++20 的协程为我们提供了一种全新的异步编程范式,让复杂的异步逻辑变得像同步代码一样直观。掌握 `co_await`、`co_yield` 与 `co_return` 的使用,以及 Awaitable 接口的实现,能够让你在网络、文件、数据库等多种场景中高效编写可维护、可扩展的异步程序。希望本文能成为你踏入协程世界的起点,祝编码愉快!

发表评论