**如何使用C++20的协程实现异步IO**

C++20标准引入了协程(coroutines)这一强大的语言特性,提供了一套统一的异步编程模型。与传统的回调或线程池相比,协程让异步代码更接近同步写法,极大提升可读性和维护性。本文以文件读取为例,演示如何使用C++20协程实现异步IO。

1. 关键概念回顾

关键术语 说明
co_await 等待一个可等待对象(awaitable)完成,并返回其结果。
co_yield 在生成器协程中产生一个值,暂停执行直到下次 co_await
co_return 结束协程并返回值。
std::experimental::generator 标准库提供的生成器实现(可在 std::experimental 命名空间下使用)。

2. 设计异步读取接口

我们先定义一个可等待对象 async_read,它包装了异步文件读取操作:

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

struct async_read {
    struct promise_type {
        std::string result;
        std::future<std::string> get_return_object() {
            return promise.get_future();
        }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_value(std::string value) {
            result = std::move(value);
        }
        void unhandled_exception() {
            std::terminate();
        }
    };

    std::future<std::string> promise;
};

async_read async_read_file(const std::string& path) {
    co_return []() -> std::string {
        std::ifstream in(path, std::ios::binary);
        if (!in) return "";
        return std::string((std::istreambuf_iterator <char>(in)),
                           std::istreambuf_iterator <char>());
    }();
}
  • promise_type 用于存储协程的结果。
  • co_return 把读取的内容返回给协程的调用者。
  • promise.get_future() 提供一个 std::future,调用者可以等待或查询状态。

3. 使用协程进行异步读取

int main() {
    auto handle = async_read_file("sample.txt");
    std::future<std::string> fut = handle.promise;

    // 可以在此处执行其他任务
    std::cout << "正在读取文件...\n";

    // 等待结果
    std::string content = fut.get();
    std::cout << "文件内容长度: " << content.size() << " 字节\n";
    return 0;
}

此代码在 async_read_file 调用时立即返回协程句柄,主线程可以继续执行,而文件读取在后台进行。通过 future.get() 阻塞等待结果,或者使用 future.wait_for() 进行非阻塞查询。

4. 多文件异步读取

协程天然支持并发。我们可以一次启动多个 async_read_file 并使用 std::vector<std::future<std::string>> 收集结果:

std::vector<std::string> paths = {"a.txt", "b.txt", "c.txt"};
std::vector<std::future<std::string>> futures;

for (const auto& p : paths) {
    auto handle = async_read_file(p);
    futures.push_back(std::move(handle.promise));
}

for (auto& fut : futures) {
    std::string data = fut.get();
    std::cout << "文件读取完成, 大小: " << data.size() << '\n';
}

这样每个文件的读取都在各自的协程中异步进行,充分利用多核 CPU 的并行性。

5. 与标准库协程适配

C++20 标准库提供了 std::experimental::generatorstd::future 的互操作方式。若想将异步IO与生成器结合,例如逐行读取文件:

#include <experimental/generator>

std::experimental::generator<std::string> async_lines(const std::string& path) {
    std::ifstream in(path);
    std::string line;
    while (std::getline(in, line)) {
        co_yield line;  // 每读取一行返回
    }
}

此生成器可以在异步上下文中使用 co_await 读取每一行,而不需要一次性读完全部内容。

6. 小结

  • 协程 让异步代码可读性大幅提升。
  • async_read_file 展示了如何包装异步IO为可等待对象。
  • 多协程并发 通过 std::futuregenerator 轻松实现。
  • 与标准库 结合使用 std::experimental 组件,代码更简洁、类型安全。

在 C++20 环境下,协程已成为处理高性能异步 IO 的首选方案。通过以上示例,你可以快速上手并将协程融入自己的项目中,进一步提升代码质量与运行效率。

发表评论