在 C++20 里,协程(coroutines)成为了标准库的一部分,为异步编程提供了更简洁、直观的语法。协程本质上是能够“暂停”和“恢复”的函数,结合任务调度器(scheduler)可以实现高效的异步任务执行。下面我们详细探讨协程的基本语义、实现细节以及如何利用标准库中的 std::generator 与 std::task 来完成异步任务调度。
1. 协程的基本语义
1.1 co_await、co_yield 与 co_return
co_await:在协程内部暂停,等待某个 awaitable(可等待对象)完成后再恢复。返回值会被作为 await 表达式的结果。co_yield:在生成器(generator)中使用,向调用者返回一个值,并暂停协程;下次调用时从此处恢复。co_return:结束协程,返回最终结果。
1.2 协程的生命周期
协程在第一次调用时会生成一个“协程对象”,其内部保存了状态机的栈帧和协程句柄(std::coroutine_handle<>)。协程的状态(Suspended、Ready、Executing、Completed)由编译器自动管理。
2. 标准库中的协程类型
2.1 std::generator
#include <experimental/generator>
std::experimental::generator <int> range(int start, int end) {
for (int i = start; i < end; ++i) {
co_yield i;
}
}
std::generator 让我们能像遍历容器一样使用协程产生的值。
2.2 std::task
#include <experimental/task>
std::experimental::task <int> async_add(int a, int b) {
co_return a + b;
}
std::task 表示一个异步结果,常与协程调度器配合使用。
3. 任务调度器实现
一个简单的协程调度器可以是一个事件循环,将待执行的协程封装成 std::function<void()>,并放入队列中。下面给出一个最简实现:
#include <queue>
#include <functional>
#include <coroutine>
class SimpleScheduler {
public:
void schedule(std::coroutine_handle<> coro) {
tasks.emplace([coro](){ coro.resume(); });
}
void run() {
while (!tasks.empty()) {
auto task = std::move(tasks.front());
tasks.pop();
task();
}
}
private:
std::queue<std::function<void()>> tasks;
};
在协程中,当遇到 co_await 时,awaitable 可以在其 await_suspend 方法中将协程句柄交给调度器,等待完成后再恢复。
4. 组合示例:异步文件读取
假设我们要实现一个异步文件读取,返回文件内容字符串。下面演示如何结合 std::task 与调度器实现:
#include <iostream>
#include <fstream>
#include <experimental/task>
#include <experimental/coroutine>
class FileReadAwaitable {
public:
FileReadAwaitable(const std::string& path) : file_path(path) {}
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
// 这里使用简单的同步读取,实际可换成 I/O 线程或异步 I/O
scheduler.schedule(h);
}
std::string await_resume() {
std::ifstream file(file_path);
std::string content((std::istreambuf_iterator <char>(file)),
std::istreambuf_iterator <char>());
return content;
}
private:
std::string file_path;
};
std::experimental::task<std::string> async_read_file(const std::string& path) {
std::string data = co_await FileReadAwaitable(path);
co_return data;
}
int main() {
SimpleScheduler scheduler;
auto read_task = async_read_file("example.txt");
// 将协程句柄放入调度器
scheduler.schedule(read_task.holder().promise().get_handle());
scheduler.run();
std::cout << "File content:\n" << read_task.result() << std::endl;
return 0;
}
说明:
FileReadAwaitable在await_suspend中把协程句柄交给调度器,调度器随后会在事件循环中恢复协程。async_read_file通过co_await调用 awaitable,获取文件内容后返回。
5. 小结
C++20 的协程为异步编程提供了强大而灵活的基础设施。通过 std::generator 和 std::task,配合自定义的调度器,你可以在不依赖第三方框架的情况下实现高性能的异步任务调度。理解协程的生命周期与 awaitable 的实现细节是关键,掌握后可进一步探索如网络 I/O、定时器、并发任务池等更复杂的场景。