C++20中协程的实际应用场景与实现细节

协程(coroutine)是C++20标准中引入的强大语言特性,旨在简化异步编程、状态机实现以及流式数据处理。相比传统的回调和线程模型,协程可以在单线程中实现轻量级的并发,减少上下文切换开销,提高代码可读性。本文将从协程的基础概念、典型使用场景、实现原理以及实践示例四个方面进行系统阐述,帮助读者快速掌握C++20协程的实战技巧。

1. 协程基础概念

名称 说明
co_await 暂停协程并等待一个 awaitable 对象完成
co_yield 暂停协程并返回一个值给调用者,协程可以继续执行
co_return 结束协程并返回结果
awaiter 任何可以被 co_await 的对象,必须实现 await_ready, await_suspend, await_resume
promise 协程与外部交互的桥梁,负责管理协程的生命周期与结果

协程函数返回一个特殊的类型 std::future 或自定义的 generatortask 等。

2. 典型应用场景

2.1 异步 I/O

在高并发网络服务器中,协程可以以同步方式书写异步 I/O 代码。例如使用 asio::awaitable 或自定义 awaiter 对 std::socket 进行 co_await,避免回调地狱。

asio::awaitable <void> session(tcp::socket sock) {
    std::string data;
    while (true) {
        std::size_t n = co_await sock.async_read_some(asio::buffer(data),
                                                      asio::use_awaitable);
        if (n == 0) break;
        co_await sock.async_write_some(asio::buffer(data),
                                       asio::use_awaitable);
    }
}

2.2 生成器(Generator)

协程的 co_yield 可以实现惰性序列生成,替代传统的迭代器。例如 Fibonacci 序列:

generator<std::uint64_t> fib(uint64_t count) {
    std::uint64_t a = 0, b = 1;
    for (uint64_t i = 0; i < count; ++i) {
        co_yield a;
        std::tie(a, b) = std::make_pair(b, a + b);
    }
}

2.3 状态机(State Machine)

协程能够天然实现有限状态机。每次 co_await 可以表示一次状态转移,代码结构清晰:

task <void> traffic_light() {
    while (true) {
        // Green
        co_await std::chrono::seconds(5);
        // Yellow
        co_await std::chrono::seconds(2);
        // Red
        co_await std::chrono::seconds(5);
    }
}

3. 协程实现原理

协程的编译实现相当复杂,但对开发者而言只需要关注接口。下面简述核心流程:

  1. Promise 对象:编译器为每个协程生成一个 promise_type,该对象保存协程状态、返回值等。调用者通过 co_await 获得 promise_type::get_return_object(),通常是 std::future 或自定义包装。

  2. Awaiter 机制co_await 会在编译期调用 awaitable 对象的 await_ready() 判断是否立即完成。若返回 false,则 await_suspend() 被调用,协程挂起;当外部事件完成后,协程通过 await_resume() 恢复。

  3. 协程句柄:`std::coroutine_handle

    ` 用于操作协程(resume、destroy)。`co_return` 触发 `promise_type::return_value` 并标记协程完成。
  4. 栈帧重排:编译器将协程中的局部变量拆分成两部分:静态(存放在堆或栈的分配器里)和动态(存放在协程帧)。这使得协程可以被挂起后再恢复时,状态保持一致。

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

下面演示如何使用 C++20 协程实现异步文件读取,结合 asioawaitable 和自定义 awaiter。

#include <asio.hpp>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <string>

using asio::awaitable;
using asio::use_awaitable;

// 自定义 awaiter:异步读取文件
class async_file_reader {
public:
    async_file_reader(const std::filesystem::path& path, std::size_t chunk_size)
        : path_(path), chunk_size_(chunk_size) {}

    bool await_ready() const noexcept { return false; }

    void await_suspend(std::coroutine_handle<> h) const {
        std::thread([h, this] {
            std::ifstream file(path_, std::ios::binary);
            if (!file) { h.resume(); return; }

            std::vector <char> buffer(chunk_size_);
            while (file.read(buffer.data(), buffer.size()) || file.gcount() > 0) {
                buffer.resize(file.gcount());
                // 这里简化,直接打印到 stdout
                std::cout.write(buffer.data(), buffer.size());
                buffer.resize(chunk_size_);
            }
            h.resume();
        }).detach();
    }

    void await_resume() const noexcept {}

private:
    std::filesystem::path path_;
    std::size_t chunk_size_;
};

awaitable <void> read_file(const std::string& filename) {
    co_await async_file_reader(filename, 8192);
}

int main() {
    asio::io_context ctx;
    ctx.co_spawn(read_file("large_log.txt"));
    ctx.run();
}

该示例展示了:

  • 自定义 awaiter:实现了 await_ready, await_suspend, await_resume
  • 异步 I/O:使用 std::thread 模拟异步读取,真正项目可结合文件 I/O 的异步 API(如 std::experimental::filesystem::async_read)。
  • 协程使用:在 main 中通过 asio::io_context::co_spawn 启动协程任务。

5. 关注点与常见陷阱

  1. 资源泄漏:协程结束后需要手动 destroy,否则堆栈帧会保留。建议使用 RAII 包装 std::coroutine_handle.
  2. 异常传播:协程内部抛出的异常会在 promise_type::unhandled_exception 处理,外部通过 std::future 获取 std::exception_ptr
  3. 性能:协程本身轻量,但 awaiter 的实现如果使用同步阻塞会导致性能下降。尽量使用真正的异步 I/O。

6. 小结

C++20 协程为异步编程、生成器和状态机提供了天然、易读的实现方案。通过掌握 awaitable, awaiterpromise_type 的协作机制,开发者可以在不牺牲性能的前提下,编写高并发、易维护的现代 C++ 代码。未来的标准扩展(如 std::ranges::views::generator)将进一步丰富协程生态,值得持续关注。

发表评论