**C++23 Coroutines:轻量级异步编程实战**

在 C++23 标准中,协程(Coroutines)作为一种语言层面的异步编程原语,已经成熟到可以直接用于生产代码。本文从协程的基本概念、实现原理,到如何在实际项目中用它来实现异步 I/O、生成器、并发管道等场景,给出完整的示例和实用技巧。


1. 协程的核心概念

术语 含义 关键字
协程函数 产生 awaitable 对象的函数 co_await, co_yield, co_return
awaiter 抽象异步操作 需要实现 await_ready(), await_suspend(), await_resume()
promise_type 协程对象的状态容器 通过 std::experimental::coroutine_handlepromise_type 交互

重要点:协程本身是同步执行的,直到遇到 co_await,此时才会挂起。挂起后,控制权返回给调用者,等到异步事件完成后再恢复。


2. 一个完整的异步 I/O 示例

下面演示如何使用 C++23 的协程与标准库的 std::experimental::net(假设已实现)实现一个简单的 HTTP GET 客户端。

#include <iostream>
#include <string>
#include <experimental/coroutine>
#include <experimental/net>

using namespace std::experimental;
namespace net = std::experimental::net;

// awaitable 代表一个异步操作
struct AsyncRead {
    net::tcp_stream stream;
    std::vector <char> buffer;
    std::experimental::coroutine_handle<> handle; // 协程句柄

    // 当异步读完成后恢复协程
    void operator()() {
        handle.resume();
    }

    // await_ready:是否同步完成
    bool await_ready() noexcept { return false; }

    // await_suspend:挂起协程,并启动异步读
    bool await_suspend(std::experimental::coroutine_handle<> h) noexcept {
        handle = h;
        stream.async_read_some(buffer.data(), buffer.size(), *this);
        return true; // 挂起
    }

    // await_resume:获取结果
    std::size_t await_resume() noexcept { return buffer.size(); }
};

async<std::vector<char>> fetch(const std::string& host, const std::string& path) {
    net::tcp_stream stream;
    stream.connect(host, "80"); // 同步 connect

    // 发送 HTTP 请求
    std::string req = "GET " + path + " HTTP/1.1\r\n"
                      "Host: " + host + "\r\n"
                      "Connection: close\r\n\r\n";
    stream.write(req.data(), req.size());

    // 等待响应
    AsyncRead reader{stream, std::vector <char>(8192)};
    co_await reader;

    // 读取完毕后返回缓冲区
    co_return std::move(reader.buffer);
}

int main() {
    auto task = fetch("example.com", "/");
    task.wait(); // 阻塞直到协程完成
    std::cout << "收到 " << task.result().size() << " 字节\n";
}

要点

  • `async ` 是 C++23 标准中的 `std::experimental::coroutine_traits` 所生成的任务类型。
  • await_readyawait_suspendawait_resume 三个函数构成 awaitable 的完整协议。
  • handle.resume() 是恢复协程的关键调用。

3. 协程生成器(Generator)

协程可以用来实现惰性序列(Generator),大大简化迭代器实现。

#include <experimental/coroutine>
#include <iostream>

template<typename T>
struct Generator {
    struct promise_type {
        T current_value;
        std::experimental::suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }
        std::experimental::suspend_always initial_suspend() { return {}; }
        std::experimental::suspend_always final_suspend() noexcept { return {}; }
        Generator get_return_object() {
            return Generator{std::experimental::coroutine_handle <promise_type>::from_promise(*this)};
        }
        void unhandled_exception() { std::terminate(); }
        void return_void() {}
    };

    using handle_t = std::experimental::coroutine_handle <promise_type>;
    handle_t coro;

    explicit Generator(handle_t h) : coro(h) {}
    ~Generator() { if (coro) coro.destroy(); }

    T next() {
        coro.resume();
        return coro.promise().current_value;
    }

    bool done() const { return !coro || coro.done(); }
};

Generator <int> fib() {
    int a = 0, b = 1;
    co_yield a;
    while (true) {
        co_yield b;
        int tmp = a + b;
        a = b;
        b = tmp;
    }
}

int main() {
    auto g = fib();
    for (int i = 0; i < 10; ++i)
        std::cout << g.next() << ' ';
}

优点

  • 惰性:每次 next() 调用才计算下一个值。
  • 状态封装:协程内部维护了迭代状态,无需手写迭代器类。

4. 并发管道(Pipeline)

协程可以串联多层处理逻辑,形成流水线(Pipeline),非常适合大规模并行数据处理。

#include <experimental/coroutine>
#include <queue>
#include <thread>
#include <atomic>

template<typename In, typename Out>
class Pipeline {
    std::queue <In> input;
    std::queue <Out> output;
    std::atomic <bool> done{false};

    struct awaitable {
        Pipeline& pipe;
        awaitable(Pipeline& p) : pipe(p) {}
        bool await_ready() noexcept { return !pipe.input.empty(); }
        bool await_suspend(std::experimental::coroutine_handle<> h) {
            std::thread([&]{
                // Simulate work
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
                pipe.input.pop();
                h.resume();
            }).detach();
            return true;
        }
        In await_resume() { return pipe.input.front(); }
    };

public:
    void push(In v) { input.push(v); }
    std::experimental::generator <Out> run() {
        while (!done || !input.empty()) {
            auto v = co_await awaitable{*this};
            // 处理
            output.push(v * 2);
            co_yield output.front();
            output.pop();
        }
    }
    void stop() { done = true; }
};

实际场景:日志收集、视频帧处理、网络流式传输等。


5. 常见陷阱与调试技巧

误区 解决方案
协程挂起后忘记恢复 通过 co_await 的 awaitable 实现 await_suspend 时务必调用 handle.resume()
资源泄漏 promise_typefinal_suspend 里记得销毁协程句柄;使用 std::experimental::coroutine_handle::destroy()
性能不佳 大量协程频繁创建/销毁会导致堆碎片;可使用协程池或预分配内存
调试困难 由于协程内部状态隐藏,建议使用 -fsanitize=address + -fno-inline,或手动打印状态日志

6. 小结

  • C++23 的协程提供了完整的异步编程模型,兼容传统同步代码,易于集成。
  • 通过实现自定义 awaiterpromise_type,可与任何异步 I/O 或任务调度系统结合。
  • 生成器、管道等模式可以让代码更简洁、可维护。

实践建议:先从简单的异步 I/O 开始,逐步扩展到协程生成器和并发管道;并关注协程池、内存管理等高级细节,才能真正发挥协程在高性能 C++ 应用中的优势。

发表评论