推荐封装成 awaitable 类型而非直接用 std::coroutine_handle,因其语义清晰、编译器可检查挂起/恢复边界,需实现 await_ready()、await_suspend()(返回可调用对象且加 noexcept)、await_resume() 三个函数。

协程入口怎么选:std::coroutine_handle 还是 co_await + awaitable?
直接用 std::coroutine_handle 手动调度是可行的,但容易漏掉恢复时机、忘记清理状态,实际项目里更推荐封装成 awaitable 类型——它天然配合 co_await,语义清晰,编译器能帮你做挂起/恢复的边界检查。
关键点在于实现 await_ready()、await_suspend() 和 await_resume() 三个成员函数。其中 await_suspend() 必须返回一个可调用对象(比如 std::coroutine_handle 或 lambda),否则协程会立刻 resume,起不到异步效果。
-
await_ready()返回true表示无需挂起(比如缓存命中或错误前置);返回false才触发挂起 -
await_suspend()里把当前协程句柄存起来,交给网络层回调时恢复;别忘了加noexcept声明,否则编译失败 -
await_resume()只负责返回结果,不能抛异常——异常必须在await_suspend()之前或之后由调用方处理
HTTP-GET 的 socket 非阻塞读怎么和协程联动?
核心不是“怎么发请求”,而是“怎么让协程在 socket 可读时自动唤醒”。Linux 下用 epoll,Windows 下用 IOCP 或 WSAEventSelect,但无论哪种,都不能在协程挂起后直接调用 recv() —— 它会阻塞线程,破坏协程轻量性。
正确做法是:发起 connect() 后,把 socket 设为非阻塞,注册到事件循环;等 epoll_wait() 或 GetQueuedCompletionStatus() 返回该 socket 可读,再调用 await_resume() 恢复协程,并执行 recv() 读取响应头/体。
- 务必检查
recv()返回值:0 表示对端关闭,-1 且errno == EAGAIN/EWOULDBLOCK表示暂无数据,需继续等待 - HTTP 响应可能分多次到达,要累积 buffer 直到解析出完整 header(含
Content-Length或Transfer-Encoding: chunked) - 不要在协程内做 DNS 解析——
getaddrinfo()是阻塞的,得用异步 DNS 库(如 c-ares)或提前 resolve 好
如何避免协程栈爆炸和内存泄漏?
每个 co_await 都会生成一个 promise 对象,默认分配在堆上。如果 HTTP 请求频繁、生命周期短,堆分配开销会明显拖慢性能,而且容易因忘记 delete 导致泄漏。
解决方案是重载 promise_type::operator new,改用内存池(比如 boost::pool 或自定义 slab allocator)。同时,promise 对象里不要持有裸指针——用 std::shared_ptr 管理 socket fd 和 buffer,确保协程销毁时资源自动释放。
- 协程函数返回类型建议用
task<:string></:string>这类自定义 task 类型,而非std::future——后者内部有 mutex 和 condition_variable,重量级且不兼容协程调度 - 别在
await_suspend()中捕获异常并吞掉;未处理异常会导致协程被销毁,但 promise 对象可能没析构完 - 调试时注意:gdb 对协程栈支持有限,
bt可能只显示__libc_start_call_main,得靠日志或libcoro提供的 hook 打点
为什么不能直接用 boost::asio::awaitable?
可以,但代价高。boost::asio 的 awaitable 依赖完整的 io_context 调度器、strand 保序、定时器服务等,对于仅需 GET 请求的轻量场景,它引入了大量不必要的抽象层和线程同步开销。
真正轻量的核心是:只关注 socket 读写 + HTTP 解析,事件循环用单线程 epoll 就够,协程调度器只需维护一个就绪队列(std::deque<:coroutine_handle>></:coroutine_handle>),resume 时 pop 并 call 即可。
- 如果你的应用已有 event loop(如 libuv、evpp),优先复用它的 callback 机制,而不是另起一套 asio
- HTTP 头解析别用正则——用状态机逐字节判断
\r\n\r\n,避免临时 string 构造和拷贝 - GET 请求 URL 编码参数必须手动 percent-encode,
std::string_view传参时注意 lifetime,别传局部变量的 view
最易被忽略的是超时控制:协程挂起后,必须在事件循环中启动一个 timerfd 或 epoll timeout,到期强制 resume 并返回错误。没人帮你做这事,得自己配。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











