在C++20标准中,协程(coroutine)被正式纳入标准库,为异步编程提供了一套统一且高效的语法与语义。本文从协程的设计哲学入手,讲解其关键概念,并通过一个可读性极强的生产者-消费者案例,演示协程在实际项目中的应用。
一、协程的核心设计理念
-
协程是一种轻量级协作式并发模型
与线程不同,协程的切换成本极低,主要靠编译器生成状态机。协程并不占用系统栈,只有在需要真正挂起时才分配必要的帧空间。 -
暂停点(yield)与恢复点(resume)
C++20 将协程分为两个角色:awaiter(等待者)和promise(承诺)。在协程函数内部,co_await、co_yield和co_return三个关键字分别对应不同的暂停点。编译器把这些关键字编译成状态机的switch语句。 -
可组合性
协程通过co_await调用另一个协程,形成链式调用。整个调用链可以在单线程中顺序执行,或者通过异步事件循环实现真正的并发。 -
与标准库无缝集成
std::future、std::promise、std::async等异步组件可以与协程直接互操作。C++20 标准库新增std::generator、std::task等类型,为协程提供了标准化的封装。
二、协程关键类型解析
| 类型 | 作用 | 典型成员 |
|---|---|---|
std::suspend_always |
总是暂停 | bool await_ready() { return false; } |
std::suspend_never |
从不暂停 | bool await_ready() { return true; } |
| `std::generator | ||
| 生成器 |T value_type; T operator*(); void resume(); bool done();` |
||
| `std::task | ||
| 任务/异步结果 |T operator()();` |
三、生产者-消费者协程案例
下面给出一个完整的示例,演示如何使用协程实现一个轻量级的生产者-消费者模型。该示例不依赖任何第三方库,仅使用 C++20 标准库。
#include <iostream>
#include <coroutine>
#include <queue>
#include <chrono>
#include <thread>
#include <optional>
// 1. 生产者协程:生成整数序列
template <typename T>
struct Generator {
struct promise_type;
using handle_type = std::coroutine_handle <promise_type>;
struct promise_type {
T value_;
std::queue <T> buffer_; // 用于缓冲
std::suspend_always yield_value() { return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
Generator get_return_object() {
return Generator{handle_type::from_promise(*this)};
}
void unhandled_exception() { std::terminate(); }
};
handle_type coro_;
explicit Generator(handle_type h) : coro_(h) {}
~Generator() { if (coro_) coro_.destroy(); }
bool next() {
coro_.resume();
return !coro_.done();
}
T get() { return coro_.promise().value_; }
};
Generator <int> produce(int start, int count, int delay_ms) {
for (int i = 0; i < count; ++i) {
co_yield start + i;
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
}
}
// 2. 消费者协程:读取并处理数据
struct Consumer {
std::queue <int> buffer_;
void add(int v) { buffer_.push(v); }
void process() {
while (!buffer_.empty()) {
int v = buffer_.front();
buffer_.pop();
std::cout << "Consumed: " << v << std::endl;
}
}
};
int main() {
auto prod = produce(1, 10, 100); // 生成 1-10,间隔 100ms
Consumer cons;
while (prod.next()) {
int val = prod.get();
cons.add(val);
cons.process(); // 立即消费
}
std::cout << "All done." << std::endl;
}
代码解读:
produce函数是一个生成器协程。每次co_yield时,协程暂停,返回当前值给外部。外部通过next()触发恢复。Consumer维护一个普通队列,模拟消费者逻辑。这里为了演示简单,直接在主循环中消费。- 通过
std::this_thread::sleep_for模拟生产过程中的耗时。
四、性能与实际部署注意事项
| 场景 | 优点 | 缺点 |
|---|---|---|
| I/O 密集型 | 低延迟、低资源消耗 | 需要事件循环或 async-io |
| CPU 密集型 | 可与线程池配合 | 协程切换本身没有加速效果 |
| 大规模并发 | 轻量级、可堆叠 | 需要手动管理协程栈大小 |
在真实项目中,建议将协程与 std::async 或自定义事件循环框架结合,以实现真正的异步 I/O。C++20 的协程提供了足够的灵活性,既可以用于实验性学习,也能直接落地到生产代码中。
五、进一步阅读与实践建议
- 《C++20协程实战》:作者从原理到实现细节,系统梳理了协程生态。
- Boost.Coroutine2:虽然 C++20 标准已内置协程,但 Boost 的实现仍然有可借鉴之处。
- 实践项目:尝试用协程重写现有的
std::thread业务,例如网络服务器、文件处理等,观察性能差异。
总结
C++20 的协程把异步编程的难度降到了极低。只要理解好暂停点、恢复点以及状态机生成的机制,就能在日常开发中写出既简洁又高效的异步代码。希望本文的案例能帮助你快速上手,并在未来的项目中充分发挥协程的优势。