在C++20中引入的协程(Coroutines)提供了一种更直观、更高效的方式来实现异步编程和惰性计算。相较于传统的回调、状态机或线程模型,协程让代码看起来像同步执行,但内部却隐藏了上下文切换、暂停与恢复的细节。本文将从基本概念入手,展示协程的核心语法,讲解典型用例,并给出实战建议与常见陷阱。
1. 协程基本概念
- 挂起点(Suspension Point):
co_await、co_yield或co_return处,协程暂停执行。 - 恢复点(Resumption Point):在挂起点返回后,协程继续执行。
- 协程句柄(Coroutine Handle):
std::coroutine_handle<>,用于管理协程的生命周期。
2. 关键关键字
| 关键字 | 作用 |
|---|---|
co_await |
暂停协程,等待异步操作完成后恢复 |
co_yield |
生成惰性序列,每次返回一个值 |
co_return |
结束协程并返回最终结果 |
co_return void |
对无返回值的协程,直接结束 |
3. 典型实现示例
3.1 伪异步 I/O 示例
#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>
struct async_io {
struct promise_type {
async_io get_return_object() { return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
async_io async_read(int duration_ms) {
std::cout << "开始读取,等待 " << duration_ms << "ms\n";
std::this_thread::sleep_for(std::chrono::milliseconds(duration_ms));
co_return;
}
使用 co_await 调用:
async_io read_task = async_read(1000);
read_task.handle.resume(); // 手动恢复
3.2 惰性序列生成
#include <iostream>
#include <coroutine>
#include <optional>
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 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 return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle <promise_type> handle;
explicit generator(std::coroutine_handle <promise_type> h) : handle(h) {}
~generator() { if (handle) handle.destroy(); }
bool next() { return handle.resume(); }
T value() const { return handle.promise().current_value; }
};
generator <int> range(int start, int end) {
for (int i = start; i < end; ++i)
co_yield i;
}
使用:
auto gen = range(0, 5);
while (gen.next()) {
std::cout << gen.value() << " ";
}
输出:0 1 2 3 4
4. 与标准库的配合
std::future+std::async已经被协程所补充。使用std::experimental::future或第三方库(如cppcoro)可以实现更细粒度的协程化。std::ranges与协程结合,构建惰性管道,例如std::views::filter+generator。
5. 性能考量
- 栈大小:协程默认使用线程栈,若需要更小栈可使用
std::experimental::coroutine_traits结合自定义promise_type。 - 挂起成本:每次
co_await会产生上下文切换,但比线程切换更轻量。避免过度频繁的挂起。 - 异常传播:协程内部的异常会在
co_await处重新抛出。请使用try/catch进行错误处理。
6. 常见陷阱
- 忘记销毁句柄:协程句柄会占用资源,务必在使用后手动
destroy()或让 RAII 自动释放。 - 协程对象复制:协程对象不支持复制,使用
std::move或直接使用句柄传递。 - 不兼容的
awaitable:自定义awaitable必须满足operator co_await并返回支持await_suspend的对象。
7. 小结
C++20 协程提供了强大的异步与惰性计算能力,既保持了同步代码的可读性,又具备高效的执行特性。通过理解挂起点、恢复点与协程句柄的关系,并配合标准库的协程工具,开发者可以在网络编程、游戏循环以及大规模数据处理等场景中写出更简洁、高效的代码。欢迎你在实际项目中尝试,将协程与传统线程模型相结合,进一步提升系统性能与可维护性。