c++20 是第一个原生支持协程的标准,引入 co_await、co_yield、co_return 及 std::coroutine_handle;c++17 及更早版本无语言级协程,第三方库仅为模拟。

协程支持从哪个C++标准开始可用
C++20 是第一个原生支持协程的标准,co_await、co_yield、co_return 关键字和 std::coroutine_handle 等基础设施都在这个版本引入。C++17 及更早版本没有语言级协程,强行用第三方库(如 Boost.ASIO 的旧版 coroutine)本质是栈切换或宏模拟,不属于真正协程——别被“coroutine”字样误导。
如果你的项目还在用 C++17 或 GCC 10 以下编译器,默认不启用 C++20 协程;需确认编译器支持并开启:-std=c++20,且 GCC ≥ 10 / Clang ≥ 13 / MSVC ≥ 19.30。
为什么不能直接用 std::coroutine 写 HTTP 请求
std::coroutine 本身只是个协议框架,不提供任何调度、I/O 或网络能力。它只定义了如何挂起/恢复执行流,但「挂起时等什么」「谁来唤醒它」「唤醒后在哪执行」全得自己实现。HTTP 请求涉及 socket 连接、DNS 解析、TLS 握手、读写缓冲,这些都必须绑定到某个事件循环(比如 libuv、io_uring 或 ASIO 的 io_context)。
常见错误是试图手动管理 std::coroutine_handle 并在阻塞 socket 上 co_await——这会卡死整个线程。真正可行的做法是:让 awaiter 在 I/O 就绪时通知协程恢复,而通知机制依赖底层异步运行时。
- libcurl + C++20 协程?不行,libcurl 默认同步,
curl_easy_perform会阻塞 - Boost.Beast +
boost::asio::use_awaitable?可以,这是目前最成熟路径 - 基于 io_uring 的自研 awaiter?可行但 Linux 5.11+ 且需手动处理 buffer 生命周期
用 Boost.Beast 实现一个可 await 的 GET 请求
Boost.Beast(v1.76+)已完整支持 C++20 协程,其 http::async_read 和 http::async_write 都返回可等待对象。关键不是“怎么发请求”,而是确保整个链路使用同一个 boost::asio::io_context 并正确驱动它。
示例片段(省略错误检查,仅展示协程结构):
auto http_get(std::string host, std::string target) -> boost::asio::awaitable<:string> {
auto executor = co_await boost::asio::this_coro::executor;
boost::asio::ip::tcp::resolver resolver{executor};
auto endpoints = co_await resolver.async_resolve(host, "http", boost::asio::use_awaitable);
boost::asio::ip::tcp::socket socket{executor};
co_await boost::asio::async_connect(socket, endpoints, boost::asio::use_awaitable);
http::request<:string_body> req{http::verb::get, target, 11};
req.set(http::field::host, host);
req.set(http::field::user_agent, "Beast");
co_await http::async_write(socket, req, boost::asio::use_awaitable);
flat_buffer buffer;
http::response<:dynamic_body> res;
co_await http::async_read(socket, buffer, res, boost::asio::use_awaitable);
co_return boost::beast::buffers_to_string(res.body().data());
}</:dynamic_body></:string_body></:string>
注意几个硬约束:
- 必须在
io_context的线程中调用co_await,否则use_awaitable无法注册回调 - 不能跨线程传递
boost::asio::awaitable对象(除非用post转移上下文) - HTTP/2、HTTPS、重定向、超时都需要额外封装,Beast 默认只做裸 HTTP/1.1
协程 HTTP 客户端最容易被忽略的资源生命周期问题
协程函数退出时,局部变量(包括 socket、buffer、resolver)会按栈顺序析构,但如果协程被挂起,这些对象必须存活到恢复执行完成。Beast 的 use_awaitable 保证了这一点——它把 awaiter 绑定到 executor 所属的 io_context,只要 io_context 活着,相关资源就安全。但如果你自己写 awaiter,就很容易出错:
- 把
std::shared_ptr<socket></socket>存进 awaiter?没问题,但记得引用计数要撑过整个异步周期 - 用栈上
flat_buffer?危险!挂起期间 buffer 可能已被销毁,必须堆分配或用boost::beast::allocate_stable - 在协程里捕获 lambda 捕获了局部变量?同样危险,尤其捕获了
this却没确保对象生命周期覆盖整个请求
轻量级不等于无代价:每个协程实例仍需约 8KB 栈空间(默认),大量并发请求时,内存占用和调度开销比传统 callback 更敏感——别只盯着语法简洁,得看实际压测表现。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











