C++20 中的协程(coroutine)如何简化异步编程?

在 C++20 标准中引入的协程(coroutine)机制为异步编程提供了一种既直观又高效的语法层面支持。相比传统的回调、状态机或第三方库(如 Boost.Asio、libuv),协程可以让代码保持同步的可读性,同时隐藏了复杂的状态管理。下面从概念、实现细节、典型使用场景以及性能考虑四个角度,来剖析 C++20 协程如何简化异步编程。

1. 协程基础

1.1 协程是什么

协程是一种“轻量级”子程序,它们可以在执行过程中暂停(co_awaitco_yield)并在稍后恢复。协程的协作方式允许在同一线程内多次切换执行流,而不需要线程切换带来的上下文切换开销。

1.2 核心语法

  • co_await:暂停协程,等待一个 awaitable 对象完成。
  • co_yield:在生成器中返回一个值并挂起,等待下一次请求。
  • co_return:返回最终结果并结束协程。
  • awaitable:实现了 await_readyawait_suspendawait_resume 三个成员函数的类型。

1.3 协程返回类型

C++20 对协程返回类型做了扩展。最常见的是:

  • `std::future ` 或 `std::shared_future`(与 “ 兼容)
  • `std::generator `(生成器)
  • 自定义 promise_type 的返回类型

2. 编写一个简单的 async 文件读取

#include <iostream>
#include <coroutine>
#include <string>
#include <fstream>
#include <filesystem>

namespace fs = std::filesystem;

// awaitable 读取文件内容
struct FileReadAwaitable {
    const std::string path;
    std::string result;

    bool await_ready() noexcept { return false; } // 总是异步
    void await_suspend(std::coroutine_handle<> h) {
        // 在一个线程池中执行实际读取
        std::thread([this, h]() {
            std::ifstream file(path);
            std::stringstream ss;
            ss << file.rdbuf();
            result = ss.str();
            h.resume(); // 读取完成后恢复协程
        }).detach();
    }
    std::string await_resume() noexcept { return std::move(result); }
};

auto async_read(const std::string& path) -> FileReadAwaitable {
    return FileReadAwaitable{path};
}

struct Task {
    struct promise_type {
        std::future<std::string> get_return_object() {
            return std::future<std::string>{std::move(continuation)};
        }
        std::suspend_never initial_suspend() noexcept { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_value(std::string val) { result = std::move(val); }
        void unhandled_exception() { std::terminate(); }

        std::promise<std::string> continuation;
        std::string result;
    };
};

Task readFileAsync(const std::string& path) {
    std::string content = co_await async_read(path);
    co_return content;
}

上述示例展示了如何用 co_await 将文件读取包装成 awaitable,并在协程内部使用 co_return 返回最终内容。注意:这里的协程返回类型是自定义的 Task,内部使用 std::promise 作为协程的完成句柄。

3. 常见的协程模式

3.1 异步 I/O

使用 asio::awaitableboost::asio::awaitable 作为 awaitable,直接在协程中 co_await I/O 操作。协程调度器会根据 I/O 完成事件恢复协程。

3.2 生成器

C++20 标准库提供了 `std::generator

`。适用于需要逐个产生值的场景,例如懒加载、无限序列。 “`cpp std::generator range(int start, int end) { for (int i = start; i pplx::task { return pplx::create_task([=]() -> std::string { // 异步查询数据库 std::string queryResult = co_await db_async_query(“SELECT * FROM users”); // 异步读取文件 std::string fileContent = co_await async_read(“welcome.html”); return queryResult + fileContent; }).then([request](std::string responseBody) { request.reply(web::http::status_codes::OK, responseBody); }); }); “` 虽然示例中使用了 `pplx::task`,但可以替换为 `std::future` 或自定义协程返回类型。关键点是 `co_await` 的使用,让异步流程像同步代码一样书写。 ## 7. 小结 C++20 的协程为异步编程提供了一种更自然、更接近同步语义的实现方式。通过 `co_await` 与 awaitable 对象,开发者可以将网络 I/O、文件操作、数据库查询等异步任务串联起来,保持代码可读性并降低错误率。结合事件循环、线程池以及第三方异步库,协程可以在高性能服务器、游戏引擎以及嵌入式系统等场景中发挥巨大的优势。 未来 C++ 标准会进一步完善协程的标准库支持(如 `std::ranges::generator`、`std::execution::par` 的协程友好实现),使得协程成为 C++ 异步编程的默认工具。对于正在考虑改用异步框架的项目,C++20 协程无疑是值得投入的一项技术。

发表评论