C++20 协程(coroutines)为语言添加了原生的异步编程支持,允许开发者以同步代码的写法来描述异步操作,从而大幅降低回调地狱、提升可读性与可维护性。本文将从协程的基础语义、实现细节、典型使用场景以及与传统 async/await 方案的比较,系统阐述协程在异步 IO 中的优势和实际应用。
1. 协程的核心概念
1.1 协程类型
- Generator:类似于 Python 的生成器,返回一个可迭代的值流。
- Task:异步任务,返回一个
std::future或std::shared_future,可在完成后获得结果。 - Awaitable:任何支持
await_suspend、await_resume的类型,协程在遇到co_await时会暂停。
1.2 关键关键词
co_await:挂起协程,等待 awaitable 完成。co_yield:在 generator 中产生一个值并暂停。co_return:结束协程,返回最终结果。
2. 协程的执行流程
- 入口:调用协程函数,返回一个 coroutine handle。
- 初始化:handle 调用
initial_suspend(),决定是否立即执行。 - 主体:执行直至遇到
co_await、co_yield或co_return。 - 挂起:在
co_await时,调用 awaitable 的await_suspend,可把 handle 交给事件循环。 - 恢复:事件循环触发时,调用
await_resume,然后继续执行。
3. 典型异步 IO 示例
下面给出一个基于 Boost.Asio 的协程读取文件内容的例子,演示如何把异步读取包装成 Task<std::string>。
#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <iostream>
#include <fstream>
#include <string>
using namespace boost::asio;
using namespace boost::asio::experimental::awaitable_operators;
// 读取文件内容的协程
awaitable<std::string> read_file(const std::string& path) {
// 创建一个异步文件描述符
int fd = ::open(path.c_str(), O_RDONLY);
if (fd < 0) throw std::system_error(errno, std::generic_category());
// 用异步文件句柄包装成 stream
io_context& ctx = co_await this_coro::executor;
// 读取大小
char buf[4096];
std::string result;
while (true) {
int n = co_await async_read(
stream_descriptor(ctx, fd),
buffer(buf),
use_awaitable);
if (n == 0) break;
result.append(buf, n);
}
::close(fd);
co_return result;
}
// 主函数
int main() {
io_context ctx;
auto fut = read_file("example.txt");
std::string content = fut.get();
std::cout << content << std::endl;
return 0;
}
要点说明
async_read通过use_awaitable标记为 awaitable,协程在这里挂起。co_await使得代码像同步读文件一样简洁。- 事件循环由
io_context负责调度。
4. 与传统 Promise/Future 的区别
| 特点 | 传统 Promise/Future | C++20 协程 |
|---|---|---|
| 代码风格 | 回调链或 .then() |
直观同步写法 |
| 资源管理 | 需要手动捕获异常、清理 | 语言层面自动完成 |
| 性能 | 多线程同步/上下文切换 | 单线程事件循环 + 挂起/恢复 |
| 可读性 | 难以跟踪 | 线性易读 |
5. 常见陷阱与注意事项
-
协程对象的生命周期
- 协程内部使用
co_await时,任何异常都会导致协程挂起对象被销毁,务必在外部捕获并处理。
- 协程内部使用
-
异常传播
co_return后的异常需通过future.get()捕获,否则会导致程序崩溃。
-
性能瓶颈
- 过度使用
co_await可能导致频繁挂起/恢复,建议将 IO 任务拆分为合理粒度。
- 过度使用
-
与库兼容
- 不是所有异步库都支持 awaitable,使用前请确认。Boost.Asio 从 1.75 开始原生支持
awaitable。
- 不是所有异步库都支持 awaitable,使用前请确认。Boost.Asio 从 1.75 开始原生支持
6. 未来展望
C++20 协程已经为异步编程奠定了基础,后续标准会进一步完善 std::coroutine_handle 的异常处理、标准库中的异步容器以及跨平台事件循环。结合现代网络框架(如 libuv、libevent)和容器化技术,协程有望成为高性能网络服务、游戏服务器、实时系统的首选技术。
结语
C++20 协程以其简洁的语义与强大的性能优势,为异步 IO 编程带来了革命性的变化。掌握协程的基本语法、调度机制以及与第三方库的配合使用,能帮助开发者构建更高效、更易维护的现代 C++ 应用。