核心是用std::async(std::launch::async)启动独立线程执行http健康检查,再以std::future::wait_for统一控制总超时,避免慢节点拖垮批量探测;需禁用库内置超时、显式配置libcurl多路复用参数、注意容器线程安全及系统级资源限制。

用 std::async + std::future::wait_for 控制单次请求超时
HTTP健康检查的核心不是发请求,而是不让一个慢节点拖垮整批探测。C++标准库不带HTTP客户端,所以得先选底层网络方式(比如 libcurl 或 boost::beast),但超时控制逻辑必须自己套在调用外层——不能依赖库的内置超时,因为它们常只管连接或首字节,不涵盖整个响应读取。
用 std::async 启动每个探测任务,再用 std::future::wait_for 统一设上限。注意:必须用 std::launch::async 策略,否则可能同步执行,失去并发意义。
-
std::async(std::launch::async, [&]() { return do_http_head(url); })—— 每个任务独立线程 - 别直接调
get(),它会阻塞到完成;改用wait_for(std::chrono::seconds(5)) == std::future_status::ready - 如果
wait_for返回timeout,记得主动取消或忽略该future(C++20前无标准取消机制,靠任务内定期检查原子标志位)
避免 libcurl 多路复用时的隐式串行化
有人用 curl_multi_perform 想“高效并发”,结果发现 10 个 URL 实际耗时接近单个 ×10——因为默认没开 CURLOPT_TIMEOUT_MS,且 DNS 解析、TCP 连接、TLS 握手全在多路复用器里排队等。
真正起效的配置组合很具体:
立即学习“C++免费学习笔记(深入)”;
- 必须设
CURLOPT_CONNECTTIMEOUT_MS(连接阶段)和CURLOPT_TIMEOUT_MS(总耗时),两者都得显式赋值,不能只设一个 - 启用
CURLMOPT_MAXCONNECTS(比如 20),否则默认只维护 5 个空闲连接,新请求要等旧连接释放 - 禁用
CURLOPT_TCP_KEEPALIVE(健康检查场景不需要长连接保活,反而增加握手开销) - 若目标全是 HTTP/1.1,关掉
CURLOPT_HTTP_VERSION的自动协商,硬设CURL_HTTP_VERSION_1_1,避免 ALPN 探测延迟
批量结果聚合时小心 std::vector 的线程安全陷阱
多个 std::async 任务结束后,要把结果(URL、状态码、耗时、是否超时)存进一个公共容器。别直接 push_back 到全局 std::vector——push_back 可能触发重分配,导致迭代器失效或数据错乱。
- 最简方案:每个任务返回
std::tuple<:string int bool long></:string>,主线程统一收集后再一次性reserve()+emplace_back - 如果必须实时写入,用
std::mutex保护容器,但别锁整个循环体;只锁push_back那一行 - 别用
std::shared_ptr<:vector></:vector>试图“线程安全”,指针本身线程安全,但内部操作仍需同步 - 更推荐返回
std::optional<result></result>,让主线程用std::move转移,避免拷贝开销
Linux 上 epoll + libcurl 多路复用的实际瓶颈点
当端点数超过 500,即使用了 curl_multi,CPU 使用率也可能飙升到 100%,但实际并发请求数卡在 30 左右——问题常不在代码,而在系统级限制。
- 检查
/proc/sys/net/core/somaxconn和/proc/sys/net/core/netdev_max_backlog,健康检查频繁建连,这两个值太小会导致内核丢包或排队 -
ulimit -n必须大于预期并发数 × 2(每个 curl handle 至少占 2 个 fd:socket + 可能的 DNS socket) - 别在循环里反复调
curl_multi_fdset+select,改用curl_multi_wait(它内部用epoll或kqueue,效率高得多) - 如果用
boost::beast,注意stream::expires_after是 per-operation 的,不是 per-connection;每次async_read或async_write都得单独设
超时逻辑永远比请求逻辑更难测全。尤其当后端返回半截响应(比如只发了 header 就断开),不同 HTTP 库对“超时”的判定差异很大——有的算成功,有的抛异常,有的卡死。这事没法靠文档猜,得用 tcpdump 抓包对照看实际行为。







