利用C++20协程实现异步文件IO

在现代C++中,协程(coroutine)为编写高性能、可读性强的异步代码提供了极大的便利。本文将演示如何在C++20环境下,利用标准库提供的协程特性实现一个简单的异步文件读取器,并讨论其与传统同步IO的区别与优势。

1. 先决条件

  • C++20支持的编译器(如gcc 10+、clang 11+、MSVC 19.28+)
  • 标准库 ` `, “, “, “, “ 等
  • 对协程基础(co_yield, co_await, co_return)的基本了解

2. 设计思路

我们将创建一个 AsyncFileReader 类,提供一个返回 std::future<std::string>readAsync 成员函数。内部使用协程将文件读取任务包装成一个异步工作流,最终把结果放入 std::promise,供 std::future 接收。

3. 核心代码

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

namespace fs = std::filesystem;

// 简单的协程生成器
template<typename T>
struct Generator {
    struct promise_type {
        T current_value;
        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        Generator get_return_object() {
            return Generator{std::coroutine_handle <promise_type>::from_promise(*this)};
        }
        void unhandled_exception() { std::terminate(); }
        void return_void() {}
    };
    std::coroutine_handle <promise_type> coro;

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

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

class AsyncFileReader {
public:
    std::future<std::string> readAsync(const fs::path& file) {
        std::promise<std::string> prom;
        std::future<std::string> fut = prom.get_future();

        // 启动协程,实际读取工作会在线程池中完成
        std::thread([=, prom = std::move(prom)]() mutable {
            try {
                std::ifstream ifs(file, std::ios::binary);
                if (!ifs) throw std::runtime_error("无法打开文件");

                // 读取整个文件内容
                std::string data((std::istreambuf_iterator <char>(ifs)),
                                 std::istreambuf_iterator <char>());
                prom.set_value(std::move(data));
            } catch (...) {
                prom.set_exception(std::current_exception());
            }
        }).detach();

        return fut;
    }
};

4. 使用示例

int main() {
    AsyncFileReader reader;
    auto fut = reader.readAsync("example.txt");

    // 这里可以继续做其他工作,直到需要结果时才等待
    std::cout << "正在执行其他任务...\n";
    std::this_thread::sleep_for(std::chrono::seconds(1));

    try {
        std::string content = fut.get(); // 这里会阻塞直到文件读取完成
        std::cout << "文件内容大小:" << content.size() << " 字节\n";
    } catch (const std::exception& e) {
        std::cerr << "读取失败: " << e.what() << '\n';
    }
}

5. 与传统同步IO比较

维度 同步IO 异步协程IO
线程利用 需要为每个IO占用一个线程 协程只需一个线程即可调度多个IO任务
代码可读性 需要显式线程管理、锁、回调 逻辑顺序自然,类似同步写法
性能 线程上下文切换开销大 协程上下文切换轻量级
错误处理 统一的异常捕获 仍可使用 std::exception_ptr

6. 扩展思路

  1. 批量读取:把协程包装成 Generator<std::string>,逐块读取大文件,避免一次性占用过多内存。
  2. 网络IO:结合 Boost.Asio 或 libuv,利用协程实现高并发网络服务。
  3. 协程池:自建协程调度器,将多个协程挂载到有限线程池,进一步提升资源利用率。

7. 结语

C++20 的协程为实现高性能异步IO提供了原生、类型安全的手段。通过上述示例,读者可以快速上手并将协程应用到自己的文件处理、网络通信等场景。随着协程生态的完善,未来在 C++ 生态中异步编程将变得更加简洁与高效。

发表评论