协程(coroutine)是 C++20 标准中一次性让你在同一线程中实现异步、事件驱动逻辑的强大工具。与传统的回调、Future 或线程相比,协程语法简洁、执行效率更高,也不需要手动管理线程池。本文从基本概念、语法结构到实际案例,带你快速入门 C++20 协程。
1. 协程的基本概念
协程是一种特殊的函数,它可以在执行过程中“挂起”(suspend)并在之后恢复(resume)。挂起点是协程的暂停点(co_await, co_yield, co_return),恢复点则是再次执行协程时从挂起点继续。协程的优势在于:
- 异步代码写作像同步:使用
co_await可以像await一样等待异步操作完成,而不需要写回调链。 - 资源共享:协程的状态(即执行栈)由编译器自动拆分为多个状态机,存放在堆或栈上,轻量高效。
- 非阻塞:协程挂起时不会占用线程,只在需要时恢复。
2. 协程的关键类型
协程需要定义返回类型,最常见的两种是:
- `std::future `:用于单个异步结果,适用于需要与其他异步操作组合的场景。
- `generator `(来自 `cppcoro` 或手写):生成器式协程,使用 `co_yield` 产生值流。
此外,还需要定义 promise type,协程编译器通过 promise_type 进行状态管理。你可以自定义 promise_type,甚至实现自定义协程库。
3. 写一个简单的异步函数
#include <coroutine>
#include <iostream>
#include <chrono>
#include <thread>
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
};
};
Task async_print(int id) {
std::cout << "Task " << id << " started\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Task " << id << " finished\n";
co_return;
}
int main() {
async_print(42); // 协程立即执行到挂起点
std::this_thread::sleep_for(std::chrono::seconds(2)); // 主线程等待
}
这里 Task 使用 std::suspend_always 在 final_suspend 处挂起,确保协程结束后被销毁。虽然 async_print 在主线程内执行,但你可以在 co_await 处挂起等待异步事件。
4. 结合 co_await 与 std::future
C++20 引入 std::future 与协程的天然集成。以下示例展示了如何在协程中等待 std::future:
#include <coroutine>
#include <future>
#include <iostream>
#include <thread>
Task wait_future(std::future <void> fut) {
co_await fut; // 等待 future 完成
std::cout << "Future completed\n";
}
int main() {
auto fut = std::async(std::launch::async, []{
std::this_thread::sleep_for(std::chrono::seconds(1));
});
wait_future(std::move(fut));
std::this_thread::sleep_for(std::chrono::seconds(2));
}
协程在 co_await fut 处挂起,直到 fut 完成。内部实现使用 std::experimental::coroutine_traits 自动推断,极大简化异步流程。
5. 生成器式协程示例
生成器可以用来一次性生成一系列值,而不需要显式存储整个容器:
#include <generator>
#include <iostream>
std::generator <int> range(int start, int end) {
for (int i = start; i < end; ++i) {
co_yield i;
}
}
int main() {
for (int n : range(0, 5)) {
std::cout << n << ' ';
}
}
生成器实现非常轻量,适用于文件行读取、数据流处理等场景。
6. 性能与注意事项
- 协程的堆分配:默认实现可能在堆上分配协程对象,若频繁创建会产生开销。可使用
std::coroutine_handle进行手动内存管理。 - 错误传播:在协程中使用
throw时,异常会被promise_type::unhandled_exception捕获,默认行为是std::terminate。可以自定义异常处理逻辑。 - 调试困难:由于编译器拆分协程,调试会话可能出现跳过代码。使用 IDE 的协程调试插件可以缓解。
7. 结束语
C++20 的协程让异步编程从“手写回调链”跃升为“可读可维护”。通过掌握 co_await、co_yield 与自定义 promise_type,你可以轻松实现网络 I/O、并发任务、流式数据处理等高级功能。下一步建议结合网络库(如 asio)和数据库驱动,构建完整的异步框架。祝你编码愉快!