不是必须,但直接返回std::future会丢失协程轻量特性;C++20协程需自定义promise_type和awaitable,使HTTP请求挂起于IO就绪点,不占用线程栈。

协程函数必须返回 std::future 或自定义 awaitable 吗?
不是必须,但直接返回 std::future 会丢失协程的轻量特性——它底层仍依赖线程池调度,且无法与 co_await 原生衔接。C++20 协程真正有价值的地方,在于自定义 promise_type 和 awaitable 类型,让 HTTP 请求能挂起在 IO 完成点,不占用线程栈。
实际做法是定义一个 http_request 类型,其 operator co_await() 返回一个自定义 awaiter,该 awaiter 在 await_ready() 中检查 socket 是否就绪,在 await_suspend() 中注册 epoll/kqueue 回调或投递到 event loop,await_resume() 返回响应结果。
-
await_suspend()必须返回std::coroutine_handle<>(不能是void),否则协程无法被正确挂起 - 不要在
await_ready()里做阻塞读写——它只应快速判断是否已就绪(比如 recv buffer 非空) - 若用 libuv 或 asio,可复用其
async_read/async_write的 handle,但需包装成符合awaitable要求的接口
如何避免 co_await http_get("https://...") 阻塞主线程?
关键不在协程语法本身,而在底层事件循环是否运行。协程挂起后控制权交还给 event loop,如果没启动 loop,co_await 就永远等不到 resume。
典型结构是:启动一个单线程 event loop(如基于 epoll_wait 或 kqueue),把 socket 设置为非阻塞,并在 await_suspend() 中把当前协程 handle 注册进事件表;当 socket 可读/可写时,loop 主动调用 resume()。
立即学习“C++免费学习笔记(深入)”;
- 切忌在
main()函数里直接co_await然后就退出——协程 handle 会被销毁,导致未定义行为 - 推荐用
static std::shared_ptr<event_loop>管理生命周期,确保所有 awaiter 都在其作用域内 - HTTP 解析(如解析 header、chunked body)必须在 resume 后同步完成,不能再次挂起——否则需要分阶段 awaitable(如
read_header()→read_body())
SSL/TLS 握手怎么和协程配合?
OpenSSL 的 SSL_connect() 和 SSL_read() 在非阻塞模式下会返回 SSL_ERROR_WANT_READ 或 SSL_ERROR_WANT_WRITE,这正是协程挂起的天然信号点。
你的 awaiter 在 await_suspend() 中,根据 OpenSSL 错误码决定注册可读还是可写事件;resume 后再次调用对应 SSL 函数,直到成功或失败。
- 不要用
SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE)以外的额外配置——部分 write 模式对协程友好,避免因缓冲区满而卡死 -
SSL_do_handshake()可能多次挂起,需在 awaiter 内部状态机记录当前阶段(handshake / read / write) - 证书验证失败不会触发挂起,而是直接在
await_resume()抛出异常,要提前设置SSL_CTX_set_verify()回调
为什么 http_get 返回值类型不能是 std::string?
因为协程函数声明必须包含 co_await 表达式,而返回 std::string 的函数无法满足协程要求的 promise type 约束。编译器会报错:function declared as a coroutine must have a return type that includes a nested 'promise_type'。
正确方式是返回一个封装了 promise_type 的类型,比如 task<http_response>,其中 task 是你实现的协程模板类,内部持有 std::coroutine_handle<promise_type>,并在 ~task() 中负责释放 handle(除非已 resume 或被 detach)。
- 别偷懒用
std::experimental::coroutines的旧版 task——C++20 标准库没有提供现成的task,必须自己写 -
task<T>的promise_type::get_return_object()必须返回task<T>自身,且return_value(T&&)存储结果到 promise 成员 - 若用户忘记
co_await而直接丢弃task对象,必须在 destructor 里调用destroy(),否则内存泄漏
最易被忽略的一点:DNS 解析默认是阻塞的。即使整个 HTTP 流程都协程化,getaddrinfo() 仍会卡住 event loop。要么用异步 DNS 库(如 c-ares),要么把 DNS 查询也包装成 awaitable,并确保它走的是非阻塞 socket + 自定义 resolver loop。


















