在 C++20 中,协程(Coroutine)被正式纳入标准库,提供了比传统回调、状态机或 Future 更直观的异步编程模型。协程的核心概念是“挂起”与“恢复”,它允许函数在执行期间暂停,等待某个事件完成后再继续执行,从而实现非阻塞 I/O、流式数据处理以及复杂的业务逻辑。以下从实现原理、使用场景、性能优势以及常见陷阱四个方面,深入剖析协程在 C++20 中的价值与实践技巧。
1. 协程的基本原理
协程本质上是一种能够在不同点挂起和恢复的函数。它在编译时被转换为一个状态机,内部保存必要的局部变量、返回点与挂起点的状态。编译器会把 co_await、co_yield、co_return 等关键字转化为对状态机的操作,从而实现暂停与继续。
- co_await:挂起当前协程,等待等待对象(如异步 I/O)完成后再恢复执行。
- co_yield:返回一个值给调用者,并挂起协程,等待下一次
next() 调用。
- co_return:结束协程,返回最终结果。
协程函数的返回类型不再是 void 或 T,而是 `std::generator
`(用于 `co_yield`)或 `std::future` / `std::task`(用于 `co_await`)。C++20 通过 `std::experimental::coroutine_handle` 提供低级接口,允许自定义协程框架。
## 2. 常见使用场景
| 场景 | 传统实现 | 协程实现 |
|——|———-|———-|
| 异步文件读写 | 回调链 / Future | `co_await async_read(file)` |
| 网络 I/O | Reactor / Proactor | `co_await async_recv(sock)` |
| 数据流处理 | 手动状态机 | `co_yield` 生成器 |
| 并行计算 | 线程池 + 条件变量 | `co_await` 并行任务 |
### 示例:异步 HTTP 客户端
“`cpp
#include
#include
#include
#include
#include
struct awaitable_socket {
int sock;
awaitable_socket(int s) : sock(s) {}
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle h) {
// 这里可以注册 epoll 或者 IOCP,完成后再 resumption
// 简化起见,直接 sleep
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::string await_resume() const noexcept {
char buf[1024];
ssize_t n = read(sock, buf, sizeof(buf));
return std::string(buf, n);
}
};
struct task {
struct promise_type {
task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
task async_http_get(const std::string& host, const std::string& path) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
// … 连接 host
std::string request = “GET ” + path + ” HTTP/1.1\r\nHost: ” + host + “\r\n\r\n”;
send(sock, request.c_str(), request.size(), 0);
std::string response = co_await awaitable_socket(sock);
std::cout