协程(coroutines)是 C++20 标准引入的一项强大特性,它允许函数在执行过程中挂起并在以后恢复,从而实现异步编程、生成器、状态机等多种高级功能。本文将从协程的核心概念、语法特性、典型使用场景以及常见陷阱展开详细讨论,并给出完整代码示例,帮助你快速上手。
1. 协程核心概念
- 挂起(suspend):协程可以在任意位置挂起(suspend)执行,并保存当前状态(局部变量、调用栈等),随后可恢复(resume)执行。
- awaiter:协程挂起或恢复时,关联的对象称为 awaiter,它提供
await_ready()、await_suspend()、await_resume()三个成员函数。 - promise:协程内部使用 promise 对象来传递返回值、异常以及协程的生命周期管理。
2. 关键语法
co_await:挂起协程,等待 awaiter 完成。co_yield:在生成器中返回一个值,同时挂起协程。co_return:结束协程并返回结果。std::suspend_always/std::suspend_never:标准 awaiter,用于控制协程何时挂起。
3. 典型使用场景
| 场景 | 用法 | 代码片段 |
|---|---|---|
| 生成器 | co_yield |
for(int i=0;i<10;i++) co_yield i; |
| 异步 I/O | co_await + std::future |
auto res = co_await async_task(); |
| 状态机 | 自定义 awaiter | while (co_await state_machine()); |
| 资源管理 | co_await std::suspend_always |
co_await std::suspend_always(); |
4. 示例:实现一个简单的异步网络请求
#include <coroutine>
#include <iostream>
#include <thread>
#include <chrono>
#include <future>
struct async_sleep {
std::chrono::milliseconds dur;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) const {
std::thread([h, d=dur](){
std::this_thread::sleep_for(d);
h.resume();
}).detach();
}
void await_resume() const noexcept {}
};
struct task {
struct promise_type {
std::future<std::string> get_return_object() {
return std::move(fut);
}
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(std::string v) { fut.set_value(v); }
void unhandled_exception() { fut.set_exception(std::current_exception()); }
std::promise<std::string> fut;
};
};
task fetch_data() {
std::cout << "Start fetching..." << std::endl;
co_await async_sleep{std::chrono::milliseconds(2000)};
std::cout << "Fetching done!" << std::endl;
co_return "Hello, Coroutine!";
}
int main() {
auto fut = fetch_data();
std::cout << "Waiting for result..." << std::endl;
std::cout << fut.get() << std::endl;
}
运行结果:
Start fetching...
Waiting for result...
Fetching done!
Hello, Coroutine!
该示例演示了如何在协程中使用 co_await 对自定义的 async_sleep 进行挂起,从而实现非阻塞的延时操作。
5. 常见陷阱与调优
- 协程过度使用导致栈空间浪费
协程挂起时会保存栈帧,过多协程会消耗大量堆栈。建议对协程粒度进行评估,必要时使用std::suspend_never控制挂起。 - 异常传播
协程内部的异常会被包装到promise的unhandled_exception(),若未正确处理会导致程序崩溃。务必在协程外部使用try/catch或检查future异常。 - 资源泄漏
协程结束前需确保所有资源已正确释放,否则会导致内存泄漏。使用 RAII 结合协程时需注意其生命周期与协程挂起点的关系。
6. 结语
C++20 的协程为异步编程提供了更直观、更高效的手段。掌握协程的基本语法、设计模式和常见陷阱后,你可以在网络 I/O、游戏循环、数据流处理等多种场景中构建更简洁、更易维护的代码。欢迎继续深入探索协程的高级特性,如 std::generator、std::task 以及与线程池、事件循环的结合。
祝你编码愉快,Happy C++20!