c++20协程需配合事件循环(如boost.asio)才能实现异步i/o,因标准库未提供调度器;co_await仅挂起/恢复控制流,不自动并发,必须手动实现awaiter三函数并确保resume调用,否则协程永久挂起。

协程支持从 C++20 开始才真正可用
C++20 引入了 co_await、co_yield、co_return 和 std::coroutine_handle 等原语,但标准库没提供现成的异步 I/O 调度器。这意味着你不能只靠 co_await 就发起 HTTP 请求——必须搭配底层事件循环(如 libuv、asio)或自定义 awaiter。
常见误区是以为写个 async_http_get 函数加 co_await 就能跑起来,结果编译失败或挂起不动。根本原因是:标准协程不绑定任何执行上下文,await_suspend 返回的 std::coroutine_handle 需要被显式唤醒。
- MSVC 19.30+、Clang 13+、GCC 11+ 才完整支持协程语法,且需开启
-fcoroutines(GCC/Clang)或/EHsc /std:c++20(MSVC) -
std::suspend_always和std::suspend_never是调试协程状态的好工具,但别直接用于生产级 awaiter - 不要在协程里调用阻塞 API(如
curl_easy_perform),否则整个线程卡死
用 Boost.Asio + C++20 协程封装 HTTP 请求
Boost.Asio 1.78+ 提供了 asio::awaitable 类型和配套调度器,是最稳妥的起点。它把 socket 操作包装成可挂起对象,配合 asio::io_context 运行。
关键不是“怎么写协程函数”,而是“怎么让网络操作可挂起”。Asio 的做法是:每个异步操作(如 async_read)返回一个 awaitable,其 await_ready 判断是否就绪,await_suspend 把协程 handle 注册进 io_context 的就绪队列。
auto http_get(std::string url) -> asio::awaitable<:string> {
auto executor = co_await asio::this_coro::executor;
asio::ip::tcp::resolver resolver{executor};
asio::ip::tcp::socket socket{executor};
auto endpoints = co_await resolver.async_resolve("httpbin.org", "80");
co_await asio::async_connect(socket, endpoints);
std::string req = "GET /get HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\n\r\n";
co_await asio::async_write(socket, asio::buffer(req));
std::array<char> buf;
auto n = co_await asio::async_read(socket, asio::buffer(buf));
co_return std::string(buf.data(), n);
}</char></:string>
- 必须用
asio::io_context::run()启动事件循环,否则协程永不恢复 -
co_await右侧必须是满足 Awaitable 概念的对象,普通函数返回值不行 - HTTP 解析(如 header 分离、状态码提取)仍需手动处理,Asio 不提供 HTTP 客户端语义
自己实现最小化 awaiter 时最容易漏掉 resume 调用
如果你不想依赖 Boost 或想控制更底层行为(比如集成到自研线程池),就得手写 awaiter。核心陷阱是:await_suspend 必须确保最终调用 handle.resume(),否则协程永远挂起。
典型错误是把回调注册到 epoll 或 IOCP 后忘记在回调里 resume——尤其是出错路径(如连接超时、DNS 失败)常被忽略。
- awaiter 的
await_suspend返回std::coroutine_handle表示“挂起并移交控制权”,返回true表示“自行决定何时 resume”,返回false表示“立刻 resume” - 网络错误(如
ECONNREFUSED)应转为 exception 并通过promise.set_exception()传递,而不是静默吞掉 - 避免在
await_resume里做耗时操作(如 JSON 解析),这会阻塞协程恢复路径
协程栈与内存分配比想象中敏感
每个协程默认使用默认分配器,但频繁创建销毁小协程(如每秒数千请求)会导致堆碎片。C++20 允许为协程指定 operator new,但必须同时提供 operator delete,且签名要匹配(含 std::coroutine_handle 参数)。
更现实的选择是:复用协程帧(frame)或用对象池管理 promise 对象。Asio 的 awaitable 内部就做了类似优化,但自定义实现时容易忽略对齐和析构顺序。
- 协程帧大小由局部变量 + promise 对象共同决定,
sizeof无法直接获取,可用__builtin_coroutine_size(GCC)或 MSVC 的__builtin_coro_size辅助估算 - 不要在协程里捕获大型对象(如
std::vector<char></char>占用数 MB),栈空间不足时会直接 abort - 调试时用
coroutine_handle::done()判断是否已结束,避免对已销毁协程调用resume()
协程真正的复杂点不在语法,而在资源生命周期与调度时机的耦合——网络就绪、定时器触发、线程切换,这些事件如何精准驱动 resume,才是轻量级异步请求落地的关键。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











