协程(Coroutine)是 C++20 标准中一次性引入的重要特性,它为异步编程、生成器、并发等场景提供了更直观、更高效的实现方式。本文将从协程的基本概念、实现机制、典型用法以及与传统异步方式的比较,帮助读者快速掌握协程的使用技巧。
1. 协程的核心概念
1.1 什么是协程
协程是一种轻量级的用户级线程,它可以在执行过程中挂起(co_await、co_yield、co_return)并在需要时恢复。与传统线程不同,协程的上下文切换成本极低,几乎是内存复制和指针更新。
1.2 协程的三大关键词
| 关键词 | 作用 | 典型用法 |
|---|---|---|
co_await |
挂起协程,等待一个 awaitable 对象完成 | int result = co_await asyncFunc(); |
co_yield |
产生一个值并挂起协程,等待下一次拉取 | co_yield i; |
co_return |
结束协程并返回最终值 | co_return sum; |
2. 协程实现机制
协程本质上是对函数的“编译后重写”。编译器会将协程函数拆分为若干个状态机(state machine),并在栈上保存必要的局部变量与执行点。每次执行到 co_* 关键词时,函数会将当前状态保存,并返回控制权;当外部再次调用时,协程会根据保存的状态继续执行。
编译器实现细节中最核心的是:
- awaiter:实现了
await_ready(),await_suspend(),await_resume()三个成员函数,用于描述 awaitable 对象的挂起与恢复。 - promise:协程的返回值、异常传播与资源管理入口。协程函数的返回类型为
std::future或自定义类型,内部包含promise_type。 - handle:
std::coroutine_handle用于控制协程的生命周期(resume()、destroy()等)。
3. 典型协程用例
3.1 异步网络请求
#include <coroutine>
#include <iostream>
#include <chrono>
#include <thread>
struct async_wait {
struct promise_type;
using handle_t = std::coroutine_handle <promise_type>;
struct promise_type {
int value_;
async_wait get_return_object() { return {handle_t::from_promise(*this)}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(int v) { value_ = v; }
void unhandled_exception() { std::terminate(); }
};
handle_t coro_;
async_wait(handle_t h) : coro_(h) {}
~async_wait() { if (coro_) coro_.destroy(); }
int get() { coro_.resume(); return coro_.promise().value_; }
};
async_wait async_sleep(int ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
co_return ms;
}
int main() {
int elapsed = async_sleep(500).get(); // 简化的同步等待
std::cout << "耗时: " << elapsed << "ms\n";
}
这段代码演示了如何使用协程模拟异步等待。真实项目中,可以将 async_sleep 替换为异步 I/O 操作,协程在等待 I/O 时挂起,避免阻塞线程。
3.2 生成器(无限序列)
#include <coroutine>
#include <iostream>
template<typename T>
struct generator {
struct promise_type;
using handle_t = std::coroutine_handle <promise_type>;
struct promise_type {
T value_;
generator get_return_object() { return {handle_t::from_promise(*this)}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(T v) {
value_ = v; return {};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
handle_t coro_;
explicit generator(handle_t h) : coro_(h) {}
~generator() { if (coro_) coro_.destroy(); }
T next() {
coro_.resume();
return coro_.promise().value_;
}
};
generator <int> natural_numbers() {
for (int i = 1; ; ++i)
co_yield i;
}
int main() {
auto gen = natural_numbers();
for (int i = 0; i < 10; ++i)
std::cout << gen.next() << " ";
}
这里演示了如何用协程实现一个生成器,支持无限序列。由于协程可以在 co_yield 时挂起,内存占用仅为当前值,效率极高。
3.3 异步管道(流)
#include <coroutine>
#include <vector>
#include <iostream>
struct async_pipe {
struct promise_type;
using handle_t = std::coroutine_handle <promise_type>;
struct promise_type {
std::vector <int> buffer_;
async_pipe get_return_object() { return {handle_t::from_promise(*this)}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(int v) {
buffer_.push_back(v);
return {};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
handle_t coro_;
explicit async_pipe(handle_t h) : coro_(h) {}
~async_pipe() { if (coro_) coro_.destroy(); }
const std::vector <int>& data() const { return coro_.promise().buffer_; }
};
async_pipe generate_data() {
for (int i = 0; i < 5; ++i)
co_yield i * 2;
}
int main() {
auto pipe = generate_data();
for (auto val : pipe.data())
std::cout << val << " ";
}
协程可以作为生产者/消费者模式的核心,使得数据流的产生与消费解耦。
4. 协程与传统异步编程的比较
| 特性 | 传统回调 / Future | 协程 |
|---|---|---|
| 可读性 | 回调嵌套导致“回调地狱” | 代码顺序化,类似同步 |
| 错误处理 | 需要手动捕获、链式 propagate | 统一异常传播 via promise |
| 性能 | 每次回调都需要堆分配、线程切换 | 轻量级状态机,极低上下文切换成本 |
| 资源管理 | 需要显式释放 | 自动在 final_suspend 处销毁 |
5. 进一步学习资源
- 《C++20协程实战》作者:刘鑫
- cppreference.com 上的
std::coroutine_handle与promise_type章节 - GitHub 上的 “cppcoro” 项目,提供了更高级的协程工具库
- 《Effective Modern C++》第二版中的协程章节
6. 结语
协程是 C++ 语言的一次重大演进,为异步编程提供了更自然、更高效的工具。掌握了协程后,你可以轻松实现异步网络、事件驱动、生成器、流式处理等多种模式,而不再需要依赖繁琐的回调或线程池。希望本文能帮助你快速入门并在实际项目中发挥协程的威力。