在 C++20 中,协程(coroutines)被正式纳入语言标准,提供了一套低成本的非阻塞编程模型。下面将演示如何利用协程实现一个简易的异步任务调度器,并通过示例说明其使用方式。
1. 关键概念回顾
- co_await:挂起当前协程,等待 awaiter 完成后恢复执行。
- co_yield:向调用方产生一个值,协程被挂起,下一次
co_await会继续执行。 - co_return:结束协程,返回最终结果。
调度器的核心是一个事件循环,负责存储待执行的协程,并在适当时机恢复它们。
2. 基础组件
2.1 Awaitable 结构体
#include <coroutine>
#include <iostream>
#include <queue>
#include <chrono>
#include <thread>
#include <optional>
struct SleepAwaiter {
std::chrono::milliseconds duration;
SleepAwaiter(std::chrono::milliseconds d) : duration(d) {}
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) const noexcept {
std::thread([h, this]() {
std::this_thread::sleep_for(duration);
h.resume(); // 计时结束后恢复协程
}).detach();
}
void await_resume() const noexcept {}
};
2.2 Task 句柄
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
};
};
3. 调度器实现
class Scheduler {
std::queue<std::coroutine_handle<>> tasks;
public:
void add(std::coroutine_handle<> h) {
tasks.push(h);
}
void run() {
while (!tasks.empty()) {
auto h = tasks.front();
tasks.pop();
if (!h.done()) h.resume();
}
}
};
Scheduler g_scheduler; // 全局调度器
4. 协程工作示例
Task async_print(const std::string& msg, std::chrono::milliseconds delay) {
co_await SleepAwaiter(delay); // 等待指定时间
std::cout << msg << std::endl; // 输出信息
}
4.1 启动协程
int main() {
// 将协程句柄注册到调度器
g_scheduler.add(async_print("Hello, coroutine!", std::chrono::milliseconds(500)).get_return_object().handle);
g_scheduler.add(async_print("Goodbye!", std::chrono::milliseconds(1000)).get_return_object().handle);
g_scheduler.run(); // 事件循环开始
std::this_thread::sleep_for(std::chrono::seconds(2)); // 防止主线程提前退出
return 0;
}
运行结果:
Hello, coroutine!
Goodbye!
5. 进一步扩展
- 优先级队列:用
std::priority_queue替换std::queue,为协程设置优先级。 - 超时机制:在
await_suspend里使用计时器检测超时,若超时则直接恢复协程并抛出异常。 - 线程池:把
std::thread换成固定大小的线程池,提高资源利用率。
6. 小结
C++20 的协程让异步编程变得更加直观和轻量。上述例子演示了如何构建最小可行的调度器并与协程配合使用。通过进一步改造,你可以轻松搭建出支持 IO、多线程、优先级调度等高级功能的异步框架。