C++20 引入了协程(coroutine)这一强大的语法特性,为异步编程、生成器以及延迟计算提供了更自然、更高效的实现方式。本文将从协程的基础语义入手,介绍常见的使用模式,并结合代码示例展示如何在实际项目中轻松利用协程提升代码质量和性能。
1. 协程的核心概念
| 术语 | 说明 |
|---|---|
co_await |
暂停协程,等待异步操作完成 |
co_yield |
暂停协程,产生一个值给调用者 |
co_return |
结束协程并返回最终结果 |
promise_type |
协程的状态管理器,决定协程的生命周期、异常处理以及返回值 |
协程本质上是一个“可暂停可恢复”的函数,C++ 编译器会将其拆分成若干状态机步骤,内部使用 promise_type 来保存协程的局部状态。
2. 协程的三大典型场景
2.1 生成器(Generator)
#include <coroutine>
#include <iostream>
#include <vector>
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(), !handle.done(); }
T value() { return handle.promise().current_value; }
};
Generator <int> count_to_n(int n) {
for (int i = 0; i < n; ++i) co_yield i;
}
int main() {
for (auto g = count_to_n(5); g.next(); ) {
std::cout << g.value() << ' ';
}
}
此代码展示了如何使用 co_yield 创建一个懒惰生成器,调用者在 next() 时才会执行生成器内部的下一步。
2.2 异步 I/O
#include <coroutine>
#include <iostream>
#include <chrono>
#include <thread>
#include <future>
struct AsyncTask {
struct promise_type {
std::future <void> get_future() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
using handle_type = std::coroutine_handle <promise_type>;
};
AsyncTask async_sleep(int ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
co_return;
}
int main() {
auto t = async_sleep(1000);
std::cout << "Done\n";
}
虽然这里使用的是同步 sleep,但在真实网络或文件 I/O 场景中,可以把 co_await 与自定义 awaitable 组合,真正实现非阻塞异步。
2.3 延迟计算与管道(Pipelining)
利用 co_await 以及 std::experimental::generator(如果使用 TS),可以将多个协程串联成管道,实现流式数据处理。示例代码略长,这里仅给出思路:
- 输入协程:从文件或网络读取数据块,使用
co_yield输出。 - 处理协程:接收输入块,进行解码、压缩等处理,使用
co_yield输出处理结果。 - 输出协程:接收处理结果,写入磁盘或网络。
每一步都是独立的协程,天然支持并发与背压(back-pressure)控制。
3. 与 STL 的配合
C++20 的 std::generator(尚在实验阶段)与 std::ranges 结合使用,可以写出简洁的管道式代码:
auto numbers = std::views::iota(1, 10);
auto squares = numbers | std::views::transform([](int x){ return x * x; });
for (auto v : squares) std::cout << v << ' ';
如果想要把 std::generator 的结果与 ranges 结合,可写一个适配器:
template<typename Generator>
auto to_view(Generator&& g) {
struct iterator {
using value_type = decltype(g.value());
Generator* gen;
bool operator==(std::default_sentinel_t) const noexcept { return gen->handle.done(); }
value_type operator*() const noexcept { return gen->value(); }
void operator++() { gen->next(); }
};
struct view {
Generator gen;
iterator begin() { return {&gen, false}; }
std::default_sentinel_t end() { return {}; }
};
return view{std::forward <Generator>(g)};
}
这样就可以在 ranges 语义下直接使用协程生成器。
4. 性能与注意事项
- 堆分配:协程的
promise_type及状态机默认放在堆上(通过std::coroutine_handle)。若协程频繁创建,可能导致内存碎片。可以自定义operator new或使用std::pmr::monotonic_buffer_resource。 - 异常处理:协程中的异常会跳到
unhandled_exception。若不想终止程序,需在promise_type中实现自定义异常捕获。 - 调试支持:大多数 IDE 目前对协程的调试仍有限。建议使用
-fno-exceptions或-fcoroutines-ts进行调试前的预处理。 - 与多线程结合:协程本身是单线程执行的,若需要在多线程中共享数据,仍需使用锁或
std::atomic。但可结合std::async与co_await,把线程池交给协程。
5. 小结
- 协程是 C++20 的重要语言特性,为生成器、异步 I/O 与流式管道提供了天然语法。
- 核心语义:
co_await、co_yield、co_return与promise_type。 - 典型场景:生成器、异步 I/O、数据流管道。
- 与 STL:结合
ranges与实验性的generator可写出更简洁、可组合的代码。 - 性能注意:堆分配、异常处理、调试支持。
掌握协程后,你将能够以更自然的方式书写高性能、易维护的异步程序,为未来 C++ 标准的发展打下坚实基础。祝你编码愉快!