在 C++20 标准中,协程(coroutines)被正式加入语言层面,为编写异步、非阻塞代码提供了强大且简洁的工具。本文将以一个实用示例——基于协程的异步任务调度器,展示如何利用 C++20 标准库中的 std::suspend_always、std::suspend_never 以及 std::experimental::coroutine_handle 等关键组件,构建一个轻量级的任务队列,支持异步执行和结果回调。
1. 协程基础
协程是一种能够在执行过程中被挂起和恢复的函数。相比传统的线程,协程开销极小,适合高并发场景。C++20 通过 co_await、co_yield、co_return 关键字以及协程返回类型(如 std::future、std::generator)实现协程机制。
核心类型:
- `std::coroutine_handle `:代表协程句柄,允许对协程进行挂起、恢复和销毁。
std::suspend_always:协程始终挂起,常用于实现自定义 awaitable。std::suspend_never:协程从不挂起,常用于同步实现。
2. 设计思路
我们将构建一个简单的 Task 类,代表一次异步计算;Scheduler 类管理所有待执行的 Task,并在事件循环中依次恢复它们。关键点:
- Task 的返回类型为 `std::future `,内部使用 `co_await` 等待异步事件。
- Scheduler 使用
std::queue<std::coroutine_handle<>>存储待恢复的协程句柄。 - 通过
co_await自定义 awaitable,协程在等待事件时挂起,Scheduler 负责触发恢复。
3. 代码实现
#include <coroutine>
#include <future>
#include <queue>
#include <iostream>
#include <chrono>
#include <thread>
#include <optional>
struct Scheduler {
static Scheduler& instance() {
static Scheduler s;
return s;
}
void schedule(std::coroutine_handle<> h) {
queue_.push(h);
}
void run() {
while (!queue_.empty()) {
auto h = queue_.front();
queue_.pop();
h.resume();
}
}
private:
std::queue<std::coroutine_handle<>> queue_;
};
struct SleepAwaitable {
std::chrono::milliseconds duration;
Scheduler& scheduler = Scheduler::instance();
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
std::thread([=]{
std::this_thread::sleep_for(duration);
scheduler.schedule(h);
}).detach();
}
void await_resume() noexcept {}
};
using Task = std::future <void>;
Task async_sleep(std::chrono::milliseconds ms) {
co_await SleepAwaitable{ms};
co_return;
}
Task async_task(int id, int work_units) {
std::cout << "Task " << id << " start\n";
for (int i = 0; i < work_units; ++i) {
co_await SleepAwaitable{std::chrono::milliseconds(100)};
std::cout << "Task " << id << " progress " << i+1 << "/" << work_units << "\n";
}
std::cout << "Task " << id << " finish\n";
co_return;
}
int main() {
Scheduler::instance().schedule(async_task(1, 5).operator co_await().handle_);
Scheduler::instance().schedule(async_task(2, 3).operator co_await().handle_);
Scheduler::instance().schedule(async_sleep(std::chrono::milliseconds(200)).operator co_await().handle_);
Scheduler::instance().run(); // 进入事件循环
std::this_thread::sleep_for(std::chrono::seconds(3)); // 让后台线程有足够时间完成
return 0;
}
说明
SleepAwaitable:自定义 awaitable,内部使用std::thread模拟异步等待。实际项目中可替换为 I/O 完成事件或定时器回调。Scheduler::schedule:把挂起的协程句柄放入队列。Scheduler::run:循环恢复协程,直至队列为空。因为协程在挂起时会重新排入队列,事件循环会不断推进。
4. 优点与扩展
- 轻量级:协程本质上是状态机,内存占用极低。
- 可组合:不同 Task 可互相
co_await,形成复杂工作流。 - 易于调试:协程的挂起点和恢复点可以通过 IDE 或日志清晰追踪。
扩展思路:
- 任务优先级:改用优先级队列。
- 事件源:把
SleepAwaitable替换为网络 I/O 或文件 I/O 的异步事件。 - 异常传播:在
Task中捕获并转为std::future的异常状态。
5. 结语
C++20 的协程为异步编程带来了革命性的简化。通过结合事件循环与自定义 awaitable,开发者可以轻松实现高性能、可维护的异步任务调度器。未来随着标准库完善,协程将成为 C++ 异步生态的基石,值得每个 C++ 开发者深入掌握。