在 C++20 中,协程(coroutines)被正式纳入标准库,成为一种强大的异步编程工具。通过协程可以以同步代码的语法编写异步逻辑,极大提升代码可读性与可维护性。下面我们以实现一个简单的异步文件读取为例,系统阐述从协程定义、状态机实现到异步 I/O 的完整流程。
1. 协程的基本概念
协程是支持挂起(co_await)与恢复(co_return、co_yield)的函数。其执行过程被分为若干 suspend points(挂起点),在这些点上协程可以将控制权交还给调用者,随后根据需要恢复执行。协程的实现核心是 状态机:编译器会把协程代码拆分为若干状态块,生成一个隐式的 promise_type,并在其中维护协程的运行状态。
2. 协程的返回类型
C++20 标准库提供了几种协程返回类型,最常用的有:
- `std::future `:与 std::async 配合使用,支持同步等待结果。
- `std::generator `:支持 `co_yield`,可生成序列。
- `std::task `(在 “ 中):更轻量的协程包装,适合自定义 I/O 逻辑。
下面我们使用 std::future<std::string>,并配合 std::experimental::generator 用于读取文件行。
3. 异步 I/O 的实现思路
在标准 C++17 之前,异步 I/O 需要依赖第三方库(如 Boost.Asio、libuv)。C++20 协程本身并不提供 I/O 接口,但可以与 事件循环 结合,例如:
- 使用
std::async或自定义线程池包装底层同步 I/O,转为异步。 - 使用第三方事件循环(如
libuv、asio)的异步接口,配合协程挂起。
这里演示一个简化的例子:通过 std::async 读取文件,然后用 co_await 等待完成。
4. 代码实现
#include <iostream>
#include <fstream>
#include <string>
#include <future>
#include <coroutine>
#include <experimental/generator>
namespace coro = std::experimental;
// ① 读取单行的异步协程
coro::generator<std::string> asyncReadLines(const std::string& path) {
// 创建一个同步任务来读取文件
auto readTask = std::async(std::launch::async, [&path]() {
std::ifstream file(path, std::ios::binary);
if (!file) return std::vector<std::string>{};
std::vector<std::string> lines;
std::string line;
while (std::getline(file, line)) {
lines.push_back(line);
}
return lines;
});
// 等待异步任务完成
co_await std::experimental::suspend_always{}; // 这里可替换为真正的 I/O 事件挂起
// 获取结果
std::vector<std::string> lines = readTask.get();
for (const auto& l : lines) {
co_yield l; // 逐行产生
}
}
// ② 主程序:消费协程
int main() {
std::string filePath = "sample.txt";
std::cout << "开始异步读取文件:" << filePath << "\n";
for (const auto& line : asyncReadLines(filePath)) {
std::cout << line << "\n";
}
std::cout << "文件读取完毕。\n";
return 0;
}
说明
-
asyncReadLines:使用std::async创建后台线程读取文件,返回一个std::vector<std::string>。随后在协程中co_await一个suspend_always(示例占位符,实际使用中可以改为真正的 I/O 事件挂起)。完成后遍历行并通过co_yield逐行返回。 -
事件循环:如果你使用的是
asio或libuv,可以把co_await替换为co_await asio::async_read_until或相似接口,挂起当前协程,等待 I/O 完成后恢复。 -
返回类型:这里使用
coro::generator<std::string>,适合需要按序生成结果的场景。若只想一次性获得完整结果,可直接返回std::future<std::string>。
5. 性能与优化
- 减少拷贝:
std::async创建的线程会把文件内容复制到内存,若文件巨大可考虑使用内存映射(mmap)或按块读取。 - 事件驱动:在高性能服务器中,建议使用事件循环与协程结合,避免每次 I/O 都创建线程。
asio::awaitable与co_await可直接挂起在 OS 的事件上。 - 异常处理:协程支持
try/catch,在co_return或co_yield前捕获异常,并通过promise_type的unhandled_exception传递。
6. 结语
C++20 协程为异步编程提供了更直观的语法,配合现代 I/O 框架即可实现高并发、低延迟的异步服务。虽然标准库本身尚未内置 I/O 事件循环,但通过与第三方库结合,你可以轻松构建可维护且性能优秀的异步 C++ 应用。希望本文的示例能为你上手协程与异步 I/O 打下基础。