C++20 引入了协程(coroutine)机制,极大地简化了异步编程与生成器的实现。本文将从协程的基础原理入手,逐步展开示例代码,展示如何在实际项目中高效使用协程。
一、协程基础概念
-
协程与线程
- 协程是轻量级的用户态函数,能够在任意点挂起并恢复,而不需要操作系统上下文切换。
- 协程在同一线程内执行,通过
co_await、co_yield、co_return三个关键字实现挂起、产出值和返回结果。
-
关键字
co_await:等待一个异步操作完成,挂起当前协程。co_yield:向调用者产出一个值,挂起当前协程。co_return:返回协程最终结果,结束协程。
-
awaiter
- 一个符合 Awaiter 协议的对象(实现
await_ready、await_suspend、await_resume)决定了协程挂起与恢复的行为。
- 一个符合 Awaiter 协议的对象(实现
二、协程的典型应用场景
| 场景 | 协程优势 |
|---|---|
| 异步 I/O | 减少回调嵌套,提升可读性 |
| 数据流处理 | 生成器式流,按需生成数据 |
| 任务调度 | 将任务拆分为协程块,易于并发管理 |
| 并行算法 | 通过协程控制执行顺序,避免锁 |
三、代码实战:异步网络请求
假设我们使用 cppcoro 库(已在 C++20 环境下编译)来实现一个简单的 HTTP GET 请求。
#include <cppcoro/http_server.hpp>
#include <cppcoro/http_client.hpp>
#include <cppcoro/cancellation_source.hpp>
#include <cppcoro/task.hpp>
#include <iostream>
#include <string_view>
using namespace cppcoro;
// 简单异步 HTTP 客户端
cppcoro::task<std::string> async_get(const std::string_view url)
{
// 创建 HTTP 客户端
http_client client{url};
// 发送 GET 请求
co_await client.send_get();
// 读取响应体
std::string body;
while (!client.is_done())
{
auto chunk = co_await client.read_chunk();
body.append(chunk.begin(), chunk.end());
}
co_return body; // 返回完整响应
}
// 主函数
int main()
{
// 启动协程任务
auto task = async_get("http://example.com");
// 运行事件循环
while (!task.is_done())
{
task.resume();
}
// 打印结果
std::cout << "Response body:\n" << task.get() << std::endl;
}
关键点解析
co_await client.send_get():发送请求并挂起,直到服务器响应。client.is_done():检查是否还有数据可读。client.read_chunk():读取一块数据,返回awaitable<std::string_view>。task.resume():手动驱动协程,直到完成。
四、生成器式协程:按需生成 Fibonacci 数列
生成器是协程最经典的用法之一。下面演示如何使用 co_yield 生成斐波那契数列。
#include <cppcoro/producer_consumer_queue.hpp>
#include <cppcoro/task.hpp>
#include <iostream>
cppcoro::task <void> fibonacci(cppcoro::producer_consumer_queue<long long> &queue, int n)
{
long long a = 0, b = 1;
for (int i = 0; i < n; ++i)
{
co_yield b; // 产出当前值
long long next = a + b;
a = b;
b = next;
}
queue.close(); // 关闭队列,表示生成完毕
}
int main()
{
cppcoro::producer_consumer_queue<long long> queue{5};
// 启动生成器协程
auto prod_task = fibonacci(queue, 10);
// 消费者
while (!queue.is_closed())
{
auto val = queue.pop();
std::cout << val << " ";
}
std::cout << "\n生成器已完成。" << std::endl;
}
五、协程与传统回调的对比
| 方式 | 代码可读性 | 错误易发点 | 性能开销 |
|---|---|---|---|
| 回调 | 低(多层回调) | 易出现回调地狱 | 轻量(无挂起) |
| 协程 | 高(顺序式写法) | 需要正确管理 awaitable | 轻量(协程栈共享) |
六、实战建议
- 保持协程短小:避免协程过长导致栈空间浪费。
- 使用
awaitable封装复杂逻辑:将异步操作抽象为 awaitable,保持协程主体清晰。 - 避免阻塞:协程挂起期间不允许阻塞 I/O,否则会阻塞事件循环。
- 测试:使用
cppcoro::test::unit_test或 Google Test 结合coroutine_test工具,确保协程正确恢复。
七、总结
C++20 的协程为异步编程提供了更直观、可维护的方式。通过掌握 co_await、co_yield、co_return 等关键字,以及 awaiter 的实现原则,开发者可以轻松编写高性能、可读性强的异步代码。随着协程生态的完善(如 cppcoro、boost::asio 协程化等),未来的 C++ 应用将越来越多地依赖协程实现并发与异步需求。