协程(coroutine)在C++20标准中正式引入,为传统的同步和异步编程带来了革命性的变化。相较于传统的线程和回调机制,协程以轻量级、可读性强、资源占用低的方式,实现了非阻塞的执行流。本文将从协程的基本概念入手,讲解如何使用C++20协程实现一个异步任务调度器,并进一步探讨其在并发编程中的优势与应用场景。
1. 协程基础
C++20中的协程是由 co_await、co_yield、co_return 这三个关键字实现的。协程函数的返回类型必须是可被 co_await 的对象,常见的如 std::future、std::generator 或自定义的 awaitable。
std::future <int> async_add(int a, int b) {
co_return a + b; // 直接返回结果
}
上面代码会在内部生成一个状态机,co_return 触发协程结束并把结果包装成 `std::future
`。
## 2. 构建一个简单的任务调度器
### 2.1 任务包装
首先我们需要一个通用的 `Task` 类型,包装任何可被 `co_await` 的协程。
“`cpp
template
class Task {
public:
struct promise_type {
T value_;
std::exception_ptr exception_;
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
Task get_return_object() {
return Task{ std::coroutine_handle
::from_promise(*this) };
}
void unhandled_exception() { exception_ = std::current_exception(); }
void return_value(T val) { value_ = std::move(val); }
};
using handle_type = std::coroutine_handle
;
explicit Task(handle_type h) : handle_(h) {}
Task(const Task&) = delete;
Task(Task&& other) : handle_(other.handle_) { other.handle_ = nullptr; }
~Task() { if (handle_) handle_.destroy(); }
T get() {
if (handle_.promise().exception_)
std::rethrow_exception(handle_.promise().exception_);
return std::move(handle_.promise().value_);
}
private:
handle_type handle_;
};
“`
### 2.2 事件循环
为了在单线程中实现并发,需要一个事件循环。下面是一个最简版的事件循环,它会执行所有可就绪的任务。
“`cpp
class EventLoop {
public:
void run() {
while (!tasks_.empty()) {
auto task = std::move(tasks_.front());
tasks_.pop_front();
task(); // 触发协程
}
}
void schedule(Task
task) {
tasks_.push_back(std::move(task));
}
private:
std::deque> tasks_;
};
“`
### 2.3 例子:异步IO模拟
假设我们需要模拟一个异步IO操作,例如读取文件。我们用 `std::this_thread::sleep_for` 来表示等待。
“`cpp
Task
async_read(int bytes) {
// 模拟异步等待
std::this_thread::sleep_for(std::chrono::milliseconds(100));
co_return bytes; // 读取成功,返回读取字节数
}
“`
## 3. 协程与线程的比较
| 特点 | 线程 | 协程 |
|——|——|——|
| 开销 | 高(栈、上下文切换) | 低(仅存储状态) |
| 可读性 | 代码顺序性差 | 代码顺序性好 |
| 并发模型 | 多核共享 | 单核协作 |
| 错误处理 | 传统异常/信号 | 统一异常捕获 |
协程通过在单线程中切换执行点,天然避免了锁竞争和死锁问题。但在多核并行加速场景下,仍需配合多线程或分布式方案。
## 4. 实际应用场景
1. **网络服务器**:基于协程的网络框架(如 cpp-httplib、Boost.Asio 的协程模式)能让每个请求占用极小的栈空间,提升吞吐量。
2. **游戏引擎**:协程可以实现游戏逻辑的“顺序执行”,如动画、物理模拟,减少回调地狱。
3. **数据处理管道**:使用 `co_yield` 可以构建生成器式的数据流,实现按需消费、惰性计算。
## 5. 小结
C++20协程为并发编程带来了全新的视角。通过协程与事件循环相结合,我们可以在单线程中实现高并发、低延迟的异步程序。虽然协程在多核并行方面并不直接提供加速,但它们可以作为更高级并发抽象的基础,帮助程序员写出更简洁、易维护的代码。
从现在起,尝试用协程重写你手中的回调网络代码,感受一下“暂停”和“恢复”给代码结构带来的改变吧。