curl_global_init必须在主线程调用且仅一次,多线程中隐式初始化会导致确定性崩溃;每个线程须独占CURL*句柄;OpenSSL后端需注册线程锁;所有句柄必须设置CURLOPT_NOSIGNAL=1L。

curl_global_init 必须在主线程调用且仅一次
libcurl 的 curl_global_init 不是线程安全的,多线程中若多个线程同时首次调用 curl_easy_init,可能触发内部隐式初始化竞争,导致 crash 或未定义行为。这不是概率问题,而是确定性风险。
正确做法是:程序启动时,在主线程显式调用一次 curl_global_init(CURL_GLOBAL_ALL);退出前,在主线程调用 curl_global_cleanup()。不要把它塞进某个线程初始化函数里,也不要每个线程都试一次。
- 如果使用静态链接或容器环境,
CURL_GLOBAL_ALL是最稳妥的选择(含 SSL、DNS、socket 等全部子系统) - 返回值必须检查:
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { /* 初始化失败,不能继续 */ } - 即使只用 HTTP(不用 HTTPS),也建议用
CURL_GLOBAL_ALL,避免后续加功能时漏掉 SSL 初始化而报CURLE_UNSUPPORTED_PROTOCOL
每个线程必须独占一个 CURL* 句柄
句柄(CURL*)不是线程安全的资源。共享句柄会导致内存踩踏、回调错乱、curl_easy_perform 返回随机错误码(比如 CURLE_AGAIN 或 CURLE_SEND_ERROR),甚至静默失败。
典型错误写法是全局声明一个 CURL* g_curl,然后所有线程都往上面 setopt + perform —— 这在高并发下必崩。
立即学习“C++免费学习笔记(深入)”;
父母的功课——育儿心理学对话支持技能(心虫增强版)。提供结构化对话、情绪识别、场景匹配与安全检测;可选Python脚本(scripts/)在SKILL_DIR/data/本地存储评估历史、洞察与会话状态,不对外传输。核心路径:觉察(看见防御)→接纳(慈悲是……
- 每个线程应在自己的上下文里调用
curl_easy_init(),获取专属句柄 - 句柄生命周期应与线程任务绑定:任务开始时 init,结束时
curl_easy_cleanup() - 不要把句柄存入线程局部存储(TLS)后长期复用,除非你严格控制该 TLS 变量的生命周期和重用逻辑
HTTPS 多线程需 OpenSSL 线程锁注册
当 libcurl 后端使用 OpenSSL(常见于 Linux/macOS),OpenSSL 自身不是线程安全的。不注册锁回调,多线程 HTTPS 请求会因内部全局状态冲突而 crash,现象常为 SIGSEGV 或 SIGABRT,堆栈里看不到你的代码。
注意:mbedtls 或 Schannel(Windows)不需要此步骤,但 OpenSSL(尤其是 1.0.x/1.1.x)必须处理。
- 在主线程完成
curl_global_init后、任何网络请求前,调用CRYPTO_set_locking_callback和CRYPTO_set_id_callback - 锁数组大小必须为
CRYPTO_num_locks(),不能硬编码为 10 或 100 - 回调函数中对
pthread_mutex_t的 lock/unlock 必须严格匹配mode & CRYPTO_LOCK判断,否则死锁 - 务必在程序退出前调用
CRYPTO_set_locking_callback(NULL)并销毁锁数组,否则进程无法干净退出
超时设置必须配合 CURLOPT_NOSIGNAL=1L
默认情况下,CURLOPT_TIMEOUT 或 CURLOPT_CONNECTTIMEOUT 会启用 alarm + siglongjmp 实现,而这套机制在多线程中完全不可靠——siglongjmp 会破坏其他线程的栈帧,导致 core dump 无有效调用栈。
这不是“偶尔出问题”,而是只要触发超时,就大概率崩溃,且难以复现和定位。
- 所有线程创建的句柄,都必须在
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L)设置为 1 - 该选项必须在
curl_easy_perform前设置,且不能依赖默认值 - 副作用是 DNS 解析不再受超时控制(底层 getaddrinfo 不响应
setsockopt(SO_RCVTIMEO)),若 DNS 服务异常缓慢,请求会卡住;此时可考虑用 c-ares 替代系统 resolver(需编译时启用)
多线程下 libcurl 最容易被忽略的点,不是怎么发请求,而是「谁在什么时候初始化了什么」——全局状态、句柄归属、SSL 锁、信号处理,四个环节只要漏掉一个,就不是“偶尔失败”,而是“必然在某次压测或上线后崩”。

















