协程是 C++20 的重要特性之一,它让我们可以以同步的方式编写异步代码,并显著提升并行任务的可读性与性能。本文将从协程的基本概念、实现细节到实际使用示例,系统阐述如何在 C++20 中构建高效的协程式并行模型。
1. 协程的核心概念
- Promise / Promise Object:协程函数返回一个
std::coroutine_handle<>,而协程内部维护一个 Promise 对象,它负责协程的生命周期与状态。 - Awaitable:任何可以被
co_await的对象都称为 Awaitable。它需要实现await_ready(),await_suspend(),await_resume()三个成员函数。 - Suspension Points:
co_await,co_yield,co_return触发协程挂起或返回。
协程并不是线程,而是协作式调度的轻量级任务,真正的并行实现需要与线程池或调度器配合。
2. 搭建协程调度器
下面给出一个最小化的协程调度器实现示例,支持任务的提交、挂起与恢复。
#include <coroutine>
#include <queue>
#include <functional>
#include <memory>
#include <iostream>
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
class Scheduler {
public:
void schedule(std::function<void()> fn) {
tasks.push(std::move(fn));
}
void run() {
while (!tasks.empty()) {
auto fn = std::move(tasks.front());
tasks.pop();
fn(); // 执行协程体
}
}
private:
std::queue<std::function<void()>> tasks;
};
Task是协程的返回类型。Scheduler用队列管理待执行的协程函数。
在实际项目中,可将 Scheduler 与 std::thread 结合,实现真正的并发执行。
3. Awaitable 示例:异步 I/O
假设我们使用一个简易的异步 I/O 模拟器 AsyncRead:
#include <coroutine>
#include <chrono>
#include <thread>
struct AsyncRead {
struct promise_type { /* 省略实现 */ };
using handle_type = std::coroutine_handle <promise_type>;
AsyncRead(handle_type h) : coro(h) {}
~AsyncRead() { if (coro) coro.destroy(); }
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> awaiting) {
// 模拟异步延迟
std::thread([this, awaiting]() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
awaiting.resume();
}).detach();
}
int await_resume() const noexcept { return 42; } // 假设读取到的数据
private:
handle_type coro;
};
在协程里使用:
Task async_main() {
int data = co_await AsyncRead{AsyncRead::promise_type{}};
std::cout << "Read data: " << data << '\n';
}
该示例展示了如何将异步 I/O 与协程结合,await_suspend 用来挂起协程,并在后台线程完成后恢复。
4. 并行执行多个协程
下面给出一个使用 Scheduler 并行执行多个协程的完整例子。
int main() {
Scheduler scheduler;
auto worker = [&](int id) -> Task {
for (int i = 0; i < 5; ++i) {
std::cout << "Worker " << id << " step " << i << '\n';
co_await std::suspend_always{}; // 模拟切换
}
};
for (int i = 0; i < 3; ++i) {
scheduler.schedule([&, i]() { worker(i); });
}
scheduler.run();
}
std::suspend_always{}让协程每次循环都挂起,允许调度器交替执行其他任务。- 输出会呈现多任务交错执行的效果,说明协程能有效模拟并发。
5. 性能优化建议
- 避免过度挂起:每次挂起/恢复都涉及栈切换,过多的挂起会导致性能下降。
- 使用
std::suspend_never:在不需要挂起的路径上返回suspend_never,减少调度开销。 - 线程池 + 协程:将协程与线程池结合,使用线程池执行真正的 CPU 密集任务,协程仅负责协作调度。
- 预分配栈:C++20 默认协程栈在堆上分配,若任务数量巨大,可使用自定义栈分配器降低分配次数。
6. 结语
C++20 的协程为构建高效、可读的并行程序提供了强大的工具。通过上述基础示例与调度器的搭建,你可以快速上手协程式并行开发。接下来可以进一步探索如 std::generator、std::ranges 与协程的结合,或将协程嵌入现有的网络框架(如 Boost.Asio)中,构建真正的异步 I/O 高性能应用。祝你在 C++ 协程的世界里玩得愉快!