协程(coroutine)是 C++20 引入的语法糖,为实现协作式多任务提供了原生语言支持。它使得函数可以在执行过程中“挂起”并在未来某个时点恢复,既能保持可读性,又能避免传统线程调度带来的复杂性。下面从概念、关键类型、实现细节以及一个完整示例四个部分展开说明。
1. 协程的基本概念
- 挂起(Suspend):协程执行到
co_await,co_yield或co_return时,函数体暂停,状态被保存。 - 恢复(Resume):外部或内部再次调用协程时,它从上一次挂起的位置继续执行。
- 协作式:协程的切换是由程序显式触发,而非由调度器抢占。
协程本质上是对函数控制流的拆分:把一次完整的执行过程拆成若干段,每段之间通过协程句柄(std::coroutine_handle)进行连接。
2. 关键类型与概念
| 关键词 | 作用 | 说明 |
|---|---|---|
co_await |
挂起协程,等待 awaitable 完成 | 需要实现 await_ready, await_suspend, await_resume |
co_yield |
生成值,挂起协程并返回 | 需要实现 await_suspend 以便生成器模式 |
co_return |
结束协程,返回值 | 与 return 类似,但会触发清理 |
promise_type |
协程的承诺对象,管理协程状态 | 必须定义在返回类型中 |
std::coroutine_handle |
控制协程执行的句柄 | 通过 co_await 或外部调用 resume() |
注意:协程返回类型必须提供
promise_type,并且要满足std::suspend_always/std::suspend_never等约束。
3. 典型协程实现流程
- 编译器解析:在协程体中遇到
co_await/co_yield/co_return,编译器将函数拆分为若干“悬挂点”。 - 生成
promise_type:每个协程都有一个promise_type实例,用来保存状态、返回值、异常等。 - 创建
coroutine_handle:返回类型(通常是包装器)持有 `std::coroutine_handle `,用于后续恢复与销毁。 - 挂起:在
co_await时,调用await_suspend,可能返回true/false决定是否立即恢复;在co_yield时,生成一个值并挂起。 - 恢复:外部调用
handle.resume(),函数从挂起点继续执行,直至下一个挂起或结束。
4. 示例:一个异步整数生成器
下面实现一个简单的异步整数生成器,演示协程的挂起、恢复和值传递。
#include <iostream>
#include <coroutine>
#include <optional>
struct AsyncIntGenerator {
struct promise_type {
std::optional <int> value;
std::suspend_always initial_suspend() { return {}; } // 立即挂起,等外部 resume
std::suspend_always final_suspend() noexcept { return {}; }
AsyncIntGenerator get_return_object() {
return AsyncIntGenerator{ std::coroutine_handle <promise_type>::from_promise(*this) };
}
void unhandled_exception() { std::terminate(); }
void return_void() {}
// co_yield 触发
std::suspend_always yield_value(int v) {
value = v;
return {}; // 挂起
}
};
std::coroutine_handle <promise_type> handle;
AsyncIntGenerator(std::coroutine_handle <promise_type> h) : handle(h) {}
~AsyncIntGenerator() { if (handle) handle.destroy(); }
bool resume() {
if (!handle.done()) {
handle.resume();
return true;
}
return false;
}
std::optional <int> get() const { return handle.promise().value; }
};
AsyncIntGenerator async_range(int start, int count) {
for (int i = 0; i < count; ++i)
co_yield start + i;
}
int main() {
auto gen = async_range(10, 5);
while (gen.resume()) {
std::cout << "Got: " << *gen.get() << '\n';
}
std::cout << "Finished\n";
}
输出
Got: 10
Got: 11
Got: 12
Got: 13
Got: 14
Finished
说明:
async_range在第一次resume()时执行到co_yield 10,挂起并返回值;随后每次resume()继续执行,直到循环结束。
5. 常见坑点与最佳实践
| 场景 | 坏习惯 | 推荐做法 |
|---|---|---|
| 异常处理 | 在 co_yield 时忽略异常 |
在 promise_type::unhandled_exception 里抛出/记录 |
| 资源管理 | 协程返回后忘记 handle.destroy() |
在包装器的析构中自动销毁,或使用 RAII |
| 调度 | 频繁 resume() 导致性能瓶颈 |
对于高频场景,考虑使用事件循环或线程池 |
| 线程安全 | 直接在多线程中访问同一个 coroutine_handle |
使用互斥锁或线程安全的协程框架 |
6. 进一步阅读
- Bjarne Stroustrup, The C++ Programming Language (最新版),第 42 章
cppreference.com协程章节- 《C++ Concurrency in Action》 – 对协程与传统并发的对比分析
总结
C++20 的协程提供了一种轻量级、语义清晰的方式来实现协作式多任务。通过 co_await、co_yield 与 co_return,程序员可以像写顺序代码一样表达异步逻辑,真正做到“读代码即为执行”。熟练掌握协程后,你将能在高性能服务器、游戏引擎或任何需要并发 I/O 的场景中大展拳脚。