在 C++20 中,协程(coroutines)被正式加入标准库,为异步编程提供了极大的便利。相比传统的回调或线程模型,协程既能保持代码的同步写法,又能有效管理异步任务的执行。本文从协程的基本概念、关键字使用、编写协程函数、以及常见使用场景三个部分,带你快速上手 C++20 协程。
1. 协程基础概念
协程是可以在执行过程中“挂起”并在后续恢复执行的函数。它与普通函数的区别主要体现在:
- 挂起点(
co_await,co_yield,co_return)决定了协程的暂停与恢复。 - 协程返回类型 不是普通类型,而是一个 协程句柄(
std::coroutine_handle)或一个封装的状态对象。 - 协程的生命周期由 协程状态对象 管理,编译器在内部生成相应的状态机。
2. 关键字与语法
| 关键字 | 作用 | 说明 |
|---|---|---|
co_await |
挂起协程,等待 awaitable 对象完成 | 与 async/await 类似,支持自定义 awaitable 类型 |
co_yield |
暂停协程并返回一个值给调用方 | 适用于生成器(generator)模式 |
co_return |
结束协程并返回结果 | 与普通 return 不同,需配合 awaitable 或 generator |
2.1 awaitable 对象
一个对象如果实现了以下成员函数,就能被 co_await 直接使用:
bool await_ready(); // 是否已经就绪,若为 true 则不挂起
void await_suspend(std::coroutine_handle<> h); // 挂起时调用
auto await_resume(); // 恢复后返回值
标准库提供了 std::suspend_always、std::suspend_never 等常用实现。
2.2 协程返回类型
典型的协程返回类型有三种:
- `std::future `:与线程库协作的异步结果。
- `generator `(如 `std::experimental::generator`):生成器模式。
- 自定义结构:如 `Task `,内部维护状态机并提供 `await_resume()`。
3. 编写一个简单的协程函数
下面演示一个异步等待两秒后返回字符串的协程。
#include <chrono>
#include <coroutine>
#include <future>
#include <iostream>
#include <thread>
struct SleepAwaitable {
std::chrono::milliseconds duration;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) const {
std::thread([h, dur = duration]() {
std::this_thread::sleep_for(dur);
h.resume();
}).detach();
}
void await_resume() const noexcept {}
};
std::future<std::string> async_task() {
std::cout << "Task started\n";
co_await SleepAwaitable{std::chrono::milliseconds(2000)};
std::cout << "Task resumed\n";
co_return "Hello, Coroutine!";
}
int main() {
auto fut = async_task();
std::cout << "Doing other work...\n";
std::cout << fut.get() << '\n';
}
运行结果
Task started
Doing other work...
Task resumed
Hello, Coroutine!
3.1 关键点
- 挂起:
co_await SleepAwaitable{...}触发协程暂停,await_suspend内部启动一个线程来延迟恢复。 - 恢复:线程结束后调用
h.resume(),协程继续执行co_return。 - 返回:
co_return把结果封装进std::future,主线程通过fut.get()获取。
4. 生成器(Generator)示例
生成器是一种最常见的 co_yield 用法,用于一次性生成一系列值。C++20 标准中没有正式的 generator,但实验性库 std::experimental::generator 已经可以使用。
#include <experimental/generator>
#include <iostream>
std::experimental::generator <int> range(int start, int end) {
for (int i = start; i < end; ++i)
co_yield i;
}
int main() {
for (int v : range(1, 5))
std::cout << v << ' ';
// 输出:1 2 3 4
}
co_yield 在循环中产生值,调用方通过范围 for 自动获取下一个值。协程内部维护迭代器状态,暂停与恢复由编译器完成。
5. 常见使用场景
| 场景 | 说明 |
|---|---|
| 异步 I/O | 如网络请求、文件读写,可用协程等待 I/O 完成,代码保持同步结构。 |
| 状态机 | 把复杂的状态机逻辑拆分为多个挂起点,提升可读性。 |
| 生成器 | 逐步生成序列、迭代器、流式数据处理。 |
| 协程池 | 通过协程句柄实现任务调度,降低线程上下文切换成本。 |
6. 性能与注意事项
- 编译器实现差异:不同编译器对协程支持程度不同,调试工具支持也有限。
- 堆栈分配:协程状态机默认在堆上分配,若协程大量创建需考虑内存消耗。
- 异常传播:协程内部抛出的异常会通过
await_resume传递给调用方,需使用try/catch处理。
7. 结语
C++20 协程为异步编程提供了天然的同步语法,极大提升了代码的可读性和可维护性。虽然初始学习曲线略高,但只要掌握挂起点、awaitable 对象以及协程返回类型,便能在实际项目中快速落地。希望本文能帮助你打开协程的大门,进一步探索 C++ 的现代化特性。祝编码愉快!