在现代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. 扩展思路
- 批量读取:把协程包装成
Generator<std::string>,逐块读取大文件,避免一次性占用过多内存。 - 网络IO:结合 Boost.Asio 或 libuv,利用协程实现高并发网络服务。
- 协程池:自建协程调度器,将多个协程挂载到有限线程池,进一步提升资源利用率。
7. 结语
C++20 的协程为实现高性能异步IO提供了原生、类型安全的手段。通过上述示例,读者可以快速上手并将协程应用到自己的文件处理、网络通信等场景。随着协程生态的完善,未来在 C++ 生态中异步编程将变得更加简洁与高效。