C++20 在标准库中首次正式引入协程(coroutine)概念,为异步编程和生成器提供了强大而简洁的语法。本文将从协程的基本构成、实现机制、典型用法以及常见坑点四个角度,带你快速掌握 C++20 协程,并给出实用示例。
1. 协程的基本概念
协程是一种轻量级的协作式多任务调度机制。它允许函数在执行过程中挂起(co_await、co_yield、co_return)并在后续恢复,进而实现非阻塞的异步流、延迟执行和生成器等功能。
C++20 协程的核心语法包括:
| 关键词 | 作用 | 说明 |
|---|---|---|
co_await |
挂起协程等待异步结果 | 只能出现在协程中 |
co_yield |
在协程中产生一个值,控制权交还给调用者 | 通常用于实现生成器 |
co_return |
结束协程并返回值 | 可在协程结束时使用 |
co_return; |
结束协程但不返回值 | 与 return; 类似但在协程中 |
协程函数返回的类型必须满足 协程返回类型(awaitable, generator, task 等),而不是普通的 void 或 int。
2. 协程的实现机制
在 C++20 中,编译器会把协程函数拆分为三部分:
- 协程句柄(
std::coroutine_handle):控制协程的生命周期,提供resume()、destroy()等方法。 - 协程状态机:存储挂起点、局部变量等。
- 悬空指针:在协程结束后指向完成状态。
协程的挂起与恢复是由 awaitable 对象的 await_suspend、await_resume 等函数完成的。开发者往往不需要手动管理这些细节,除非要实现自定义 awaitable。
3. 常见协程类型
| 类型 | 用途 | 示例 |
|---|---|---|
| `std::generator | ||
| 生成器,支持co_yield|generator fib()` |
||
| `std::task | ||
| 异步任务,支持co_await|task async_add(int a, int b)` |
||
| `std::future | ||
(C++20 之后) | 兼容旧式异步 |future fetch_data()` |
注意:
`、“ 等头文件中声明的。标准库实现差异较大,建议使用编译器自带的实验性实现或第三方库(如 `cppcoro`)。std::generator和std::task是在 `
4. 实际示例
下面给出一个完整的异步网络请求示例(使用 cppcoro 库),演示如何使用协程实现非阻塞 I/O。
#include <cppcoro/task.hpp>
#include <cppcoro/io_context.hpp>
#include <cppcoro/socket.hpp>
#include <cppcoro/awaitable.hpp>
#include <iostream>
using namespace cppcoro;
// 简单的异步读取函数
task<std::string> async_read(io_context& io, socket& sock, std::size_t n) {
std::vector <char> buffer(n);
auto bytes = co_await sock.read_some(buffer.data(), n);
std::string result(buffer.data(), bytes);
co_return result;
}
// 主程序
int main() {
io_context io;
socket sock(io);
// 假设已经连接到服务器
sock.connect("example.com", 80);
// 发送 GET 请求
std::string req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
sock.write_some(req.data(), req.size());
// 异步读取响应
auto fut = async_read(io, sock, 1024);
io.run(); // 运行事件循环,直到任务完成
std::cout << "Response: " << fut.get() << std::endl;
return 0;
}
说明:
async_read使用co_await挂起直到读取完成。io_context负责事件循环,类似于asio的io_context。- 通过 `task ` 的 `get()` 方法获取异步结果。
5. 常见坑点与调试技巧
-
协程返回类型错误
协程函数若不返回awaitable类型,编译器会报错。记得在返回task、generator等时使用正确的头文件。 -
生命周期管理
协程句柄若在外部保留,必须手动destroy(),否则会造成内存泄漏。使用std::shared_ptr<coroutine_handle<>>可以简化管理。 -
异常传播
协程内部抛出的异常会传播到co_await的调用点,使用try-catch捕获。若未捕获,协程会终止并抛出异常。 -
调试协程
编译器通常会在-fcoroutines下生成调试符号;可使用gdb的break *等手段查看协程状态。
6. 结语
C++20 的协程为现代 C++ 带来了强大的异步编程模型。通过合理组合 co_await、co_yield 与 co_return,我们可以轻松实现事件驱动、生成器、协程管道等复杂逻辑。熟练掌握协程的语法与底层实现,将极大提升代码的可读性与性能。祝你在协程世界里玩得开心!