C++20 引入了协程(coroutine)这一强大的异步编程工具,它使得我们可以以更直观、简洁的方式编写异步代码。下面从协程的底层实现原理、关键语法以及一个实际使用示例三个角度,帮助你快速上手。
1. 协程的实现原理
1.1 协程的基本概念
协程是一种能在执行过程中暂停并在之后恢复的函数。不同于线程,协程的上下文切换是由程序显式控制的,开销极低。C++ 协程的实现基于三大核心元素:
- co_await:挂起协程,等待一个 awaitable 对象完成后恢复执行。
- co_yield:在协程内部产生一个值,并暂停执行,等待外部再次激活。
- co_return:返回协程的最终结果,结束协程。
1.2 生成器(generator)实现
C++20 中,标准库提供了 std::generator(在 <experimental/generator> 里)来实现生成器。其底层实现大致如下:
template<class T>
class generator {
public:
struct promise_type {
T current_value;
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(T value) {
current_value = std::move(value);
return {};
}
void return_void() {}
generator get_return_object() {
return generator{std::coroutine_handle <promise_type>::from_promise(*this)};
}
};
// ...
};
- promise_type 保存协程状态,包括当前生成的值。
- yield_value 负责把值存到
current_value并暂停。 - initial_suspend 与 final_suspend 控制协程启动和结束时是否暂停。
1.3 协程的编译器支持
编译器在看到 co_await、co_yield、co_return 时会自动生成以下结构:
- Coroutine frame:在堆上分配一个结构体,保存局部变量、promise、状态机等。
- State machine:把函数体拆分成若干状态段,通过 switch+label 实现暂停与恢复。
- Coroutine handle:包装帧指针,提供
resume()、destroy()等操作。
因此,协程的运行成本相当于一个普通函数的递归调用,且无需多线程上下文切换。
2. C++20 协程核心语法
// 协程函数返回 std::generator <T>
std::generator <int> count_up_to(int n) {
for (int i = 1; i <= n; ++i) {
co_yield i; // 产生一个值并暂停
}
}
co_yield:产生值后暂停,等待外部调用resume()。co_await:对 awaitable 对象挂起,协程暂停;awaitable 必须提供await_ready、await_suspend、await_resume。co_return:返回值(如果协程返回std::generator,通常用co_return结束)。
协程对象可像普通迭代器使用:
for (int val : count_up_to(5)) {
std::cout << val << ' '; // 输出 1 2 3 4 5
}
3. 实际使用示例:异步网络请求
下面给出一个利用协程实现异步 I/O 的简易示例(使用假设的 async 网络库)。
#include <coroutine>
#include <iostream>
#include <string>
#include <experimental/generator>
// 假设的 awaitable 类型,实际项目中会使用 asio、libuv 等
struct async_read {
std::string data;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) const noexcept {
// 异步操作开始,完成后调用 h.resume()
std::thread([h, this]() {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟 I/O
data = "Hello from async!";
h.resume();
}).detach();
}
std::string await_resume() const noexcept { return data; }
};
std::coroutine_handle<> async_task();
std::generator<std::string> read_lines() {
async_read ar;
co_yield co_await ar; // 等待 async_read 完成
std::istringstream iss(co_await ar); // 解析数据
std::string line;
while (std::getline(iss, line)) {
co_yield line; // 每行返回一次
}
}
int main() {
for (auto&& line : read_lines()) {
std::cout << "Line: " << line << '\n';
}
}
说明:
async_read为自定义 awaitable,内部启动异步 I/O 线程。read_lines协程先co_await读取数据,再按行co_yield。main中像遍历容器一样消费协程产生的行,整个流程异步、非阻塞。
4. 小结
- C++20 协程 通过
co_await、co_yield、co_return等关键字,简化了异步代码的写法。 - 其实现基于编译器生成的状态机与 coroutine frame,开销低,易于理解。
- 结合 awaitable 对象,协程可以实现网络 I/O、事件驱动、协作式多任务等多种场景。
掌握协程后,你将能写出更清晰、易维护且高效的异步程序,充分利用 C++20 的现代特性。祝编码愉快!