C++20 引入了协程(coroutine)这一强大的语言特性,它彻底改变了我们在 C++ 中处理异步和延迟计算的方式。协程通过把函数分割成可以挂起和恢复的“片段”,让程序员能够以同步的写法表达异步逻辑,从而提升代码可读性和维护性。本文将从协程的基本概念、实现机制、典型用例以及与现有异步框架的对比等方面进行系统阐述,帮助读者快速上手并灵活运用协程。
一、协程的基本概念
-
挂起点(suspend point)
在协程中,co_await,co_yield,co_return是挂起点。调用这些关键字时,协程会暂时挂起,保存执行状态,等待外部条件满足后再恢复。 -
协程句柄(coroutine handle)
协程在编译时被转换为一个状态机,std::coroutine_handle用来持有并控制该状态机。通过它可以手动恢复、查询状态或销毁协程。 -
协程的返回类型
`、`std::generator`、自定义 `Awaitable` 等。C++20 通过 `co_return` 的返回值决定协程最终产生的结果。
协程需要使用特殊的返回类型,例如 `std::future -
内存占用与栈共享
与线程不同,协程的栈是由编译器在函数中自动管理的,通常是“虚拟栈”,其占用内存极小,适合大规模并发。
二、协程实现原理
-
状态机转换
编译器把协程函数转化为一个类(或结构体),其中包含operator()、promise_type、状态机变量等。每个挂起点对应一个case,执行operator()时会根据m_state进入相应分支。 -
Promise 对象
promise_type存储协程的返回值、异常信息以及挂起点的控制逻辑。它实现get_return_object,initial_suspend,final_suspend,return_value,unhandled_exception等成员函数。 -
awaiter
co_await后面跟的对象必须提供await_ready,await_suspend,await_resume三个方法,分别决定是否立即完成、挂起协程以及恢复时返回值。
三、典型用例
1. 异步 I/O
std::future<std::string> async_read(int fd, std::size_t size) {
auto buffer = std::make_shared<std::vector<char>>(size);
struct Awaiter {
int fd;
std::shared_ptr<std::vector<char>> buf;
std::promise<std::string> prom;
bool await_ready() noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
// 异步注册事件
async_register(fd, [&]{
prom.set_value(std::string(buf->data(), buf->size()));
h.resume();
});
}
std::string await_resume() { return prom.get_future().get(); }
};
co_return co_await Awaiter{fd, buffer, std::promise<std::string>{}};
}
2. 生成器(懒汉式序列)
std::generator <int> range(int start, int end) {
for (int i = start; i < end; ++i)
co_yield i;
}
3. 任务调度器
class Scheduler {
std::vector<std::coroutine_handle<>> tasks;
public:
void schedule(std::coroutine_handle<> h) { tasks.push_back(h); }
void run() {
for (auto it = tasks.begin(); it != tasks.end(); ) {
if (!(*it).done()) {
(*it)();
++it;
} else {
it = tasks.erase(it);
}
}
}
};
四、与传统异步框架的对比
| 特性 | 传统回调 | std::async / future | Boost.Asio | C++20 协程 |
|---|---|---|---|---|
| 可读性 | 低 | 中 | 中 | 高 |
| 错误传播 | 手动 | 异常 | 异常 | 异常 |
| 性能 | 高(无上下文切换) | 低(线程池) | 高 | 低(轻量级状态机) |
| 内存占用 | 低 | 高 | 低 | 低 |
| 学习曲线 | 简单 | 简单 | 中 | 中 |
协程把异步逻辑写成“同步”代码,消除了回调地狱,让错误处理与异常传递与同步代码保持一致。相比 std::future,协程不需要手动管理线程池,也不需要在回调中捕获异常。与 Boost.Asio 的异步 I/O 相比,协程可以让代码更直观、更易维护。
五、实战建议
-
避免过度使用
由于协程本身是状态机,过度拆分会导致状态机体积膨胀,反而影响性能。建议在需要真正异步、并发时才使用。 -
异常安全
在协程体内抛出的异常会被包装到promise_type::unhandled_exception,确保在co_await时通过await_resume正确抛出。 -
资源管理
通过 RAII 结合co_await的await_suspend,可以在挂起前、恢复后自动管理资源,例如锁、网络连接等。 -
与已有框架整合
许多现有框架已提供Awaitable接口,例如libuv、cppcoro,可直接在协程中使用co_await与底层事件循环交互。 -
调试工具
目前 IDE 对 C++20 协程的支持逐步完善,可通过-fcoroutines或-fcoroutines-ts进行编译调试。
六、结语
C++20 的协程为异步编程带来了革命性的简化。它把传统的“异步回调链”转化为可读性更高、错误处理更统一的同步风格代码。随着编译器与标准库的进一步成熟,协程将成为构建高性能网络服务、游戏逻辑、实时数据处理等领域不可或缺的工具。掌握协程的语义与使用场景,能够让你在未来的 C++ 开发中更加得心应手。