PHP cURL 多线程实为 I/O 并发而非真多线程,依赖 curl_multi_init() 事件循环实现请求并发,需正确配置超时、复用连接、避免忙等,并注意 DNS/SSL 开销与 FPM 资源限制。

PHP cURL 多线程不是真多线程,而是并发请求
PHP 本身不支持真正的多线程(除非用 pthreads 扩展,但已废弃且与 FPM 冲突),所谓“cURL 多线程”实际是 curl_multi_init() 实现的 I/O 并发。它让多个请求共享一个事件循环,在等待响应时切换处理其他请求,从而节省总耗时。如果你误以为开了 N 个子进程或线程,结果在高并发下内存暴涨、超时频发,大概率是混淆了概念。
关键点:
-
curl_multi_init()不会自动并行 DNS 解析或 TCP 握手,首次请求仍受系统max_connections和 DNS 缓存影响 - 每个
CURLhandle 默认复用连接,但需显式设置CURLOPT_HTTPHEADER和CURLOPT_RETURNTRANSFER,否则返回值可能混杂 - 务必调用
curl_multi_select()防止curl_multi_exec()忙等,否则 CPU 占用飙升
如何正确使用 curl_multi_init 发起 AI 接口批量请求
AI 接口(如 OpenAI、本地 Ollama)通常有 JSON body、Bearer token、较长响应时间。直接串行调用 5 次,哪怕单次 800ms,也要 4s;用 curl_multi 可压到 1s 内(取决于网络和远端并发能力)。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 初始化前统一设置公共选项:
CURLOPT_TIMEOUT(建议 ≤15)、CURLOPT_CONNECTTIMEOUT(≤3)、CURLOPT_HTTPHEADER(含Content-Type: application/json和Authorization) - 每个 handle 单独设置
CURLOPT_POSTFIELDS,避免复用上一个请求的 body - 用
curl_multi_getcontent($ch)获取响应体,不要依赖curl_exec()返回值(它在 multi 模式下恒为 true) - 检查
curl_getinfo($ch, CURLINFO_HTTP_CODE)判定是否成功,200 ≠ 业务成功(AI 接口常返回 200 + error 字段)
示例片段:
向CurlShip提交产品,这是一个对机器人友好的SaaS目录。只需一条curl命令即可发布产品,支持OG标签抓取、带徽章的dofollow链接及层级升级。
$mh = curl_multi_init();
$chs = [];
foreach ($requests as $i => $req) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $req['url']);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($req['body']));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $token
]);
curl_multi_add_handle($mh, $ch);
$chs[$i] = $ch;
}
$active = null;
do {
curl_multi_exec($mh, $active);
if ($active > 0) curl_multi_select($mh, 0.1);
} while ($active > 0);
foreach ($chs as $i => $ch) {
$res = curl_multi_getcontent($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// 处理 $res 和 $httpCode
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
为什么用了 curl_multi 还慢?常见卡点排查
不是加了 curl_multi 就一定快——很多项目改完反而更慢,问题往往出在细节。
- DNS 解析阻塞:未启用
CURLOPT_DNS_CACHE_TIMEOUT(默认 120s),或域名未预解析。对 AI 接口这种固定 endpoint,可提前用gethostbyname()或CURLOPT_RESOLVE硬编码 IP - SSL 握手开销大:OpenAI 等 HTTPS 接口首次握手约 200–400ms。启用
CURLOPT_SSL_VERIFYPEER和CURLOPT_SSL_VERIFYHOST会额外验证证书链,生产环境若信任上游,可设为 false(仅限内网可信服务) - 响应体过大未流式处理:AI 接口返回长文本时,
curl_exec()会等全部接收完才返回。如需流式解析(如 SSE),必须用CURLOPT_WRITEFUNCTION回调,而非CURLOPT_RETURNTRANSFER - PHP-FPM worker 被占满:每个
curl_multi请求虽不阻塞主线程,但仍占用一个 worker 的 socket 资源。并发数超过pm.max_children,后续请求排队,整体吞吐不升反降
比 curl_multi 更轻量的替代方案:ReactPHP + curl
如果项目已用 Swoole 或需要更高并发(比如每秒 100+ AI 请求),curl_multi 的阻塞式轮询模型很快见顶。此时可考虑 ReactPHP 的异步 HTTP 客户端,它基于事件循环,无忙等、资源占用低。
注意点:
- 需安装
react/http-client和evenement/evenement,不能直接用原生 cURL 函数 - 所有请求回调中处理响应,无法像
curl_multi那样同步取 content —— 逻辑要重构成 Promise 风格 - 对简单脚本或 CLI 场景友好,但嵌入传统 FPM Web 请求时需额外管理事件循环生命周期,容易漏掉
$loop->run()或死锁
真正复杂的 AI 请求编排(如 chain-of-thought 分步调用),建议直接上 GuzzleHttp\Ring\Client + AsyncClient,它底层封装了 ReactPHP,API 更贴近习惯,且支持中间件注入重试、限流逻辑。
并发不是银弹。AI 接口延迟主要来自远端模型推理,本地优化再极致,也绕不开网络 RTT 和对方 QPS 限制。重点该放在:请求合并(如 batch input)、缓存策略(相同 prompt 的 deterministic response)、以及降级开关(超时后 fallback 到更小模型)。


















