C++20 引入了协程(Coroutines)这一强大的语言特性,旨在简化异步编程、生成器和惰性序列的实现。与传统的回调、状态机或线程池相比,协程通过“挂起”和“恢复”的机制,让代码更像顺序执行,读写更直观。下面从概念、语法、实现细节以及常见应用场景几个角度,对 C++20 协程进行系统解析。
1. 协程的核心概念
1.1 协程(Coroutine)是什么?
协程是一种轻量级的子程序,它可以在执行过程中“挂起”(suspend)并在之后“恢复”(resume)。与线程不同,协程共享同一线程的栈空间,切换开销极低,适合需要频繁暂停/恢复的任务。
1.2 关键术语
| 术语 | 解释 |
|---|---|
co_await |
等待一个 awaitable 对象,挂起协程直至该对象完成。 |
co_yield |
在生成器中返回一个值并挂起协程,后续恢复时从此处继续。 |
co_return |
结束协程并返回最终值。 |
| Awaitable | 拥有 operator co_await 的类型,或者可以直接转换为 std::suspend_always / std::suspend_never。 |
| Promise | 协程体外部的状态对象,用来存储返回值、异常、以及协程状态。 |
2. 协程的语法与使用
2.1 简单的生成器(Generator)示例
#include <coroutine>
#include <iostream>
#include <vector>
template<typename T>
struct Generator {
struct promise_type {
T current_value;
std::suspend_always yield_value(T value) {
current_value = value;
return {};
}
std::suspend_always return_void() { return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
Generator get_return_object() {
return Generator{std::coroutine_handle <promise_type>::from_promise(*this)};
}
void unhandled_exception() { std::terminate(); }
};
using handle_type = std::coroutine_handle <promise_type>;
handle_type coro_;
explicit Generator(handle_type h) : coro_(h) {}
~Generator() { if (coro_) coro_.destroy(); }
bool next() { return coro_.resume(); }
T value() const { return coro_.promise().current_value; }
};
Generator <int> count_up_to(int limit) {
for (int i = 0; i <= limit; ++i) {
co_yield i; // 生成一个值并挂起
}
co_return; // 结束协程
}
int main() {
auto gen = count_up_to(5);
while (gen.next()) {
std::cout << gen.value() << ' ';
}
std::cout << '\n';
}
输出:
0 1 2 3 4 5
2.2 异步 I/O 协程示例
#include <coroutine>
#include <iostream>
#include <thread>
#include <chrono>
struct Task {
struct promise_type {
Task get_return_object() {
return Task{std::coroutine_handle <promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
Task async_sleep(std::chrono::milliseconds ms) {
std::cout << "Start sleep for " << ms.count() << " ms\n";
co_await std::suspend_always{}; // 模拟挂起
std::this_thread::sleep_for(ms); // 实际等待
std::cout << "Done sleeping\n";
}
int main() {
auto t = async_sleep(std::chrono::milliseconds(1000));
t.coro_.resume(); // 立即运行到挂起点
// 这里可以做其他事情
t.coro_.resume(); // 恢复执行,完成睡眠
}
以上代码中,
co_await std::suspend_always{}仅作演示。实际项目中可结合std::future、boost::asio或自定义 awaitable 对象实现真正的异步 I/O。
3. 协程实现细节
3.1 promise_type 的作用
- 存储协程内部状态(如当前值、异常等)。
- 提供协程生命周期回调(
initial_suspend,final_suspend等)。 - 与外部交互的接口(
get_return_object、return_value、yield_value等)。
3.2 handle_type 的使用
- `std::coroutine_handle ` 用于手动控制协程的挂起、恢复和销毁。
- 通过
resume()恢复协程;done()判断是否结束。
3.3 对 co_await 的扩展
自定义 awaitable 对象可以提供:
struct Awaitable {
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) const noexcept {
// 例如将 h 放入事件循环的队列
}
int await_resume() const noexcept { return 42; }
};
co_await Awaitable{} 将导致协程挂起,直到外部触发 await_resume。
4. 协程与传统方案的比较
| 方案 | 优点 | 缺点 |
|---|---|---|
| 回调 | 代码简洁 | 回调地狱,难以管理错误 |
| 状态机 | 可读性好 | 需要手动维护状态机代码 |
| 线程/线程池 | 并行执行 | 线程上下文切换成本高 |
| 协程 | 直观、轻量 | 需要 C++20 支持,学习曲线 |
4.1 典型使用场景
- 生成器(lazy sequences):如文件行读取、数值序列。
- 异步 I/O:网络请求、磁盘读写,配合
boost::asio等框架。 - 协程化状态机:复杂事件驱动系统、游戏循环。
- 并发任务调度:将协程与线程池结合,实现高效并发。
5. 实践建议
- 从生成器开始:先实现简单的
Generator,熟悉co_yield、promise_type。 - 逐步引入 awaitable:尝试实现自定义
Awaitable,并与异步事件循环结合。 - 使用库:如
cppcoro、folly::coro、libco等已有协程库,避免重复造轮子。 - 性能评估:对比线程池、回调和协程在相同工作负载下的吞吐量与延迟。
- 异常安全:记得实现
unhandled_exception(),确保协程异常能正确传播。
6. 小结
C++20 的协程为语言注入了现代异步编程的便利,既保留了 C++ 的性能优势,又显著提升了代码可读性和维护性。通过掌握 promise_type、co_await、co_yield 等核心概念,并结合 awaitable 对象,开发者可以轻松实现高效的生成器、异步 I/O 与协程化状态机。建议在实际项目中先从生成器入手,逐步扩展到更复杂的异步场景,以充分发挥协程的价值。