在 C++20 中,协程(coroutine)被引入为一种强大的语言特性,旨在简化异步编程、并发任务以及生成器等。与传统的回调函数、线程或信号量相比,协程通过在函数内部挂起和恢复执行来实现非阻塞式流程,从而大幅降低代码复杂度。下面,我们将从协程的基本概念、关键语法、实现细节以及常见使用场景等方面进行深入解析。
1. 协程的基本概念
协程是一种能在函数执行中暂停并在需要时恢复执行的函数。与普通函数不同,协程可以在任意位置挂起(co_await、co_yield、co_return),并在继续时保持内部状态。协程的生命周期可以分为以下几个阶段:
- 创建:调用协程函数返回一个协程对象(通常是一个特定的
std::generator或std::future对象)。 - 启动:调用
resume()或者使用co_await来触发执行。 - 挂起:在
co_await或co_yield处暂停,并将执行权交还给调用者或调度器。 - 恢复:再次触发
resume()或co_await,继续执行直到下一个挂起点。 - 结束:当协程执行完毕或
co_return被触发时,协程进入完成状态。
2. 关键语法与语义
2.1 co_await
- 用于等待一个可等待对象(awaitable),如
std::future、std::promise、自定义 awaitable。 - 当协程
co_await一个对象时,协程会挂起,直到被等待的对象变为就绪状态,随后恢复执行。
2.2 co_yield
- 用于生成器(generator)或可迭代对象,将一个值“产出”给调用方,并挂起协程。
- 调用方可以通过迭代器或
while循环来逐步获取协程产生的值。
2.3 co_return
- 用于返回协程的最终结果。对于
std::future或std::promise,co_return会将值传递给相关的未来对象。 - 对于生成器,
co_return终止生成过程。
2.4 co_spawn
- 在 Boost.Coroutine 或 Microsoft Concurrency Runtime 中提供,用于将协程包装成任务并提交给调度器。
3. 典型协程实现方式
3.1 std::generator
#include <iostream>
#include <coroutine>
#include <generator>
std::generator <int> fibonacci(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
int temp = a + b;
a = b;
b = temp;
}
}
3.2 std::future 与 std::async
#include <future>
#include <chrono>
std::future <int> async_task() {
co_return 42; // 直接返回值
}
3.3 自定义 awaitable
struct timer {
std::chrono::milliseconds duration;
timer(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, d=duration](){
std::this_thread::sleep_for(d);
h.resume();
}).detach();
}
void await_resume() const noexcept {}
};
async Task: sleep for 1s
4. 常见使用场景
- 异步 I/O:将文件读写、网络通信等操作包装成 awaitable,使得代码保持同步风格,易于维护。
- 事件驱动系统:使用
co_yield生成事件流,配合事件循环或消息队列实现高效事件处理。 - 生成器模式:实现无限流、懒加载等,适用于大数据处理、分布式计算。
- 协作式调度:在游戏引擎、实时系统中,协程可用于实现轻量级任务调度,减少线程上下文切换。
- 状态机实现:协程天然支持暂停和恢复,可用于实现复杂状态机的简洁代码。
5. 性能与注意事项
- 栈大小:C++20 协程默认使用“悬挂点”栈帧实现,编译器会自动管理栈空间,避免手动栈分配,但在深度递归或大量挂起时仍需关注栈溢出风险。
- 调度器:默认协程不自带调度器,需结合
std::experimental::asynchronously、Boost.Asio 或自定义调度器来实现任务分发。 - 异常传播:
co_await和co_yield均支持异常传播,未捕获异常会传播给调用者,需谨慎处理。 - 兼容性:在旧编译器或标准库中,协程尚未完全实现,需使用编译器的实验特性或第三方库。
6. 小结
C++20 协程通过语言级别的挂起与恢复机制,为异步编程提供了更自然、更高效的写法。无论是构建高性能服务器、实现复杂业务逻辑,还是简化多线程交互,协程都能显著提升代码可读性与可维护性。随着编译器与标准库的进一步完善,协程将在更广泛的应用场景中成为 C++ 开发者的首选工具。
祝你编码愉快,愿协程为你的项目带来更多可能!