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

协程是 C++20 引入的重要语言特性之一,它让异步编程、并发以及事件驱动程序的编写变得更加自然和高效。本文将从协程的基本概念、实现机制、典型使用场景以及实际代码示例四个方面,帮助你快速上手并深入理解 C++20 协程。

1. 协程概念回顾

协程是一种能够在执行过程中“挂起”和“恢复”的函数。它与传统的函数不同,协程可以在中途暂停,随后从暂停点继续执行,而不必一次性完成整个调用。协程的核心思想是将函数拆分为多个暂停点(co_awaitco_yieldco_return),每个暂停点都能在需要时保存状态,随后恢复。

在 C++20 中,协程由三大关键字实现:

  • co_await:等待一个 awaitable 对象完成,类似 await
  • co_yield:在协程中产生一个值,类似 yield
  • co_return:结束协程并返回一个结果。

协程并不是一种“线程”,它们在同一个线程中运行,只是通过协作方式实现了异步行为。协程的真正异步实现依赖于底层的 Awaitable 对象(如 std::future、自定义的异步 I/O 对象等)。

2. 协程的实现原理

协程在编译期被转换为状态机。C++ 编译器会把协程体拆分成若干个基本块,并生成一个隐藏的 promise_type,负责维护协程的状态、结果以及异常处理。

2.1 promise_type

promise_type 是协程的核心,它定义了协程运行时需要的接口:

struct my_promise {
    int value;
    std::exception_ptr eptr;

    auto get_return_object() { return coroutine_handle <my_promise>::from_promise(*this); }
    auto initial_suspend() { return std::suspend_always{}; }
    auto final_suspend() noexcept { return std::suspend_always{}; }
    void unhandled_exception() { eptr = std::current_exception(); }
    void return_value(int v) { value = v; }
};

编译器会根据 promise_type 的实现,自动生成协程入口、挂起点、恢复点以及销毁逻辑。

2.2 Awaitable 对象

任何可以被 co_await 的对象都必须满足 Awaitable 协议,即实现 await_readyawait_suspendawait_resume 三个成员函数。await_ready 用来判断是否需要挂起,await_suspend 在挂起时被调用并返回一个 std::coroutine_handleboolawait_resume 在协程恢复时返回值。

struct async_task {
    int value;
    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> h) {
        // 这里可以把 h 存储到线程池或事件循环中,等待异步事件
    }
    int await_resume() { return value; }
};

3. 协程的典型使用场景

  1. 异步 I/O
    通过协程配合事件驱动库(如 Boost.Asio、libuv)实现高并发的网络服务器。协程可以让异步 I/O 代码像同步代码一样直观。

  2. 协作式多任务
    在游戏循环、图形渲染等需要分帧执行的场景,协程可以按需切换任务,避免线程上下文切换的开销。

  3. 流式数据处理
    co_yield 让协程成为轻量级的生成器,适合处理大规模或无穷序列(如文件读取、日志处理)。

  4. 延迟执行
    使用 co_await std::suspend_always{} 或自定义延迟对象,让协程在特定条件下暂停,适合实现定时器、延迟任务等。

4. 实战示例:异步文件读取

下面演示一个简易的异步文件读取协程,使用 std::filesystemstd::ifstream 读取文件,并利用自定义 Awaitable 对象实现异步等待。

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

struct async_read {
    std::string filename;
    std::string buffer;

    struct awaiter {
        std::string& buffer;
        std::string filename;
        bool await_ready() noexcept { return false; }
        void await_suspend(std::coroutine_handle<> h) {
            std::async(std::launch::async, [this, h]() {
                std::ifstream file(filename, std::ios::binary);
                buffer.assign((std::istreambuf_iterator <char>(file)),
                              std::istreambuf_iterator <char>());
                h.resume();
            });
        }
        std::string await_resume() noexcept { return buffer; }
    };

    awaiter operator co_await() const { return {buffer, filename}; }
};

struct async_task {
    struct promise_type {
        async_task get_return_object() {
            return async_task{std::coroutine_handle <promise_type>::from_promise(*this)};
        }
        std::suspend_always initial_suspend() noexcept { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() noexcept {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle <promise_type> h;
    explicit async_task(std::coroutine_handle <promise_type> h) : h(h) {}
    ~async_task() { if (h) h.destroy(); }
};

async_task read_file(const std::string& path) {
    async_read reader{path};
    std::string data = co_await reader;
    std::cout << "File size: " << data.size() << " bytes\n";
    co_return;
}

int main() {
    read_file("example.txt");
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 等待异步完成
    return 0;
}

代码说明

  • async_read 定义了一个 Awaitable 对象,在 await_suspend 中使用 std::async 异步读取文件,读取完成后恢复协程。
  • async_task 是一个简单的协程包装器,使用 promise_type 来控制协程的生命周期。
  • read_file 是协程函数,它 co_await 异步读取器,并在读取完成后输出文件大小。

此示例演示了协程与标准库中的 std::async 配合使用的典型模式。实际项目中可以将 await_suspend 替换为网络 I/O 或数据库访问的异步回调,以实现真正的异步服务器。

5. 结语

C++20 协程为语言带来了强大的异步编程能力。它将复杂的回调链简化为直观的顺序代码,减少了错误并提升了可维护性。掌握协程的基本原理与使用场景后,你可以在网络、游戏、嵌入式系统等多种领域快速构建高性能、可伸缩的应用程序。祝你在 C++ 协程的世界里玩得开心,写出更优雅的代码!

发表评论