C++20 中的 Coroutine 基础与实战:实现异步 IO 的简易框架

在 C++20 中,协程(Coroutine)被正式引入标准库,提供了更简洁、高效的异步编程方式。相比传统的回调、Future、Promise,协程让异步代码几乎保持同步写法,极大提升可读性与可维护性。下面我们从基础语法、关键概念,到一个简易异步 IO 框架的实现,带你快速上手。


1. 协程的核心概念

术语 说明
co_await 暂停协程并等待 awaitable 对象完成。
co_yield 产出一个值,暂停协程,等待下一次 co_await
co_return 结束协程并返回最终值。
generator 通过 co_yield 实现的可迭代序列。
task 通常用于异步函数的返回类型,内部维护协程状态。

协程函数在执行时并不会立即完成,而是返回一个 悬挂(hanging)状态的对象。调用方可以对该对象做 co_awaitco_yield,从而继续执行。


2. 一个最小协程的例子

#include <coroutine>
#include <iostream>

struct simple_task {
    struct promise_type {
        simple_task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
};

simple_task hello_world() {
    std::cout << "Hello, ";
    co_return;
    std::cout << "world!"; // 这行永远不会执行
}

int main() {
    hello_world(); // 协程立即执行到 co_return
    return 0;
}

这里 simple_taskpromise_type 指定了协程的初始与最终挂起行为。std::suspend_never 表示协程不会挂起。


3. 生成器(generator)实现

#include <coroutine>
#include <iostream>
#include <vector>

template<typename T>
struct generator {
    struct promise_type {
        T current_value;
        std::vector <T> values;

        generator get_return_object() {
            return generator{std::coroutine_handle <promise_type>::from_promise(*this)};
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        std::suspend_always yield_value(T value) {
            current_value = value;
            values.push_back(value);
            return {};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle <promise_type> coro;
    generator(std::coroutine_handle <promise_type> h) : coro(h) {}
    ~generator() { coro.destroy(); }

    struct iterator {
        std::coroutine_handle <promise_type> h;
        bool operator!=(const iterator& rhs) const { return h != rhs.h; }
        void operator++() { h.resume(); }
        const T& operator*() const { return h.promise().current_value; }
    };

    iterator begin() { coro.resume(); return {coro}; }
    iterator end()   { return {nullptr}; }
};

generator <int> range(int start, int end) {
    for (int i = start; i < end; ++i) co_yield i;
}

int main() {
    for (int v : range(1, 5))
        std::cout << v << ' ';
    // 输出: 1 2 3 4
}

此实现展示了如何通过 co_yield 把值逐个产出,且协程在每次 ++ 时恢复执行。


4. 简易异步 IO 框架

下面给出一个基于 asio(Boost.Asio 或 standalone Asio)与协程的简易异步 HTTP GET 请求示例。

4.1 依赖

4.2 代码

#include <asio.hpp>
#include <asio/steady_timer.hpp>
#include <iostream>
#include <string>

using asio::ip::tcp;

// 简单的 task 返回类型
struct task {
    struct promise_type {
        std::coroutine_handle<> continuation;

        task get_return_object() { return {}; }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept {
            if (continuation) continuation.resume();
            return {};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
};

asio::awaitable<std::string> async_http_get(const std::string& host, const std::string& path) {
    using namespace asio::ip;
    auto executor = co_await asio::this_coro::executor;

    tcp::resolver resolver(executor);
    auto endpoints = co_await resolver.async_resolve(host, "http", asio::use_awaitable);

    tcp::socket socket(executor);
    co_await asio::async_connect(socket, endpoints, asio::use_awaitable);

    // 发送请求
    std::string request = "GET " + path + " HTTP/1.1\r\n" +
                          "Host: " + host + "\r\n" +
                          "Connection: close\r\n\r\n";
    co_await asio::async_write(socket, asio::buffer(request), asio::use_awaitable);

    // 读取响应
    std::string response;
    char buf[1024];
    for (;;) {
        std::size_t n = co_await socket.async_read_some(asio::buffer(buf), asio::use_awaitable);
        if (n == 0) break;
        response.append(buf, n);
    }

    co_return response;
}

int main() {
    asio::io_context io{1};

    asio::co_spawn(io, []() -> task {
        auto body = co_await async_http_get("www.example.com", "/");
        std::cout << body.substr(0, 200) << "...\n";
    }, asio::detached);

    io.run();
}

4.3 说明

  • async_http_get 是一个 awaitable 协程,内部使用 co_await 等待网络操作完成。
  • asio::co_spawn 用来启动协程,asio::detached 表示不等待完成。
  • 通过 asio::use_awaitable 将异步操作适配为可挂起的 awaitable

5. 性能与注意事项

场景 协程优势 需要注意
网络 I/O 事件驱动 + 非阻塞,CPU 利用率高 需要事件循环与 I/O 库
CPU 密集 需要线程或进程 协程本身不提升 CPU 密集任务
内存 代码量减少,函数栈不再深层 协程对象可能占用堆内存

协程是一个强大的工具,但也有学习曲线。建议从小项目实验,逐步加入更复杂的错误处理、并发控制与资源管理。


6. 结语

C++20 的协程为异步编程注入了新的活力。通过标准库的 awaitable 与第三方库如 Asio 的结合,你可以轻松实现高并发网络服务、异步文件 I/O 或游戏引擎中的任务调度。只需把 co_await 放在适当的位置,代码即可保持同步风格,既直观又高效。欢迎尝试并探索更多协程的潜能!

发表评论