php多进程curl需子进程独立初始化curlhandle并设毫秒级超时,用pcntl_waitpid(-1, $status, wnohang)轮询回收;curl_multi适合单进程高并发但需防限流与空转;proc_open稳定但开销大,须规范管道与参数;超时必须组合curlopt_connecttimeout_ms、timeout_ms及low_speed_limit/time,错误应优先检查curl_errno。

PHP多进程跑curl请求,为什么不能直接用fork加curl_exec
因为默认的curl_exec是阻塞的,子进程一卡,整个进程就挂住;而且PHP的pcntl_fork不复制cURL句柄,父子进程共享底层socket连接,容易触发Bad file descriptor或Connection reset by peer错误。
实操建议:
- 子进程创建自己的
CurlHandle,不要复用父进程的curl_init()句柄 - 每个子进程调用
curl_setopt_array()独立配置超时、重试、SSL验证等参数,尤其要设CURLOPT_TIMEOUT_MS(比如3000),避免某个请求拖垮整组并发 - 用
pcntl_waitpid(-1, $status, WNOHANG)轮询回收子进程,别用pcntl_wait硬等——否则一个慢子进程会让其他已完成的子进程白等
用curl_multi_exec替代多进程?适合什么场景
适合单进程内高并发HTTP请求,比如同时抓100个API,但所有请求目标域名不能太多(DNS解析、TCP连接池、TLS握手开销会集中爆发)。
常见错误现象:curl_multi_exec返回CURLM_CALL_MULTI_PERFORM被忽略,导致部分请求永远不发;或者没配合curl_multi_select做事件等待,CPU空转100%。
立即学习“PHP免费学习笔记(深入)”;
实操建议:
- 初始化后必须循环调用
curl_multi_exec直到返回CURLM_OK,再进curl_multi_select等待I/O就绪 - 对每个
handle单独设置CURLOPT_RETURNTRANSFER和CURLOPT_HEADER,别指望全局生效 - 如果目标接口有强限流(如每秒5次),
curl_multi反而可能触发封IP——它发包快,但服务端感知不到“节流”,得自己加usleep(200000)控速
proc_open跑curl命令行是否更稳
稳定,但成本高:每次启动新进程要加载curl二进制、解析参数、建立管道,100个请求就是100次fork+exec,内存和文件描述符消耗远超curl_multi或pcntl。
适用场景:需要严格隔离(比如不同请求走不同代理、不同CA证书)、或PHP扩展缺失(没装curl扩展但系统有curl命令)。
实操建议:
- 用
proc_open时,$descriptorspec至少定义stdin、stdout、stderr三路,否则curl报错不回传 - 命令里必须加
-s -f -m 5(静默、失败退出、5秒超时),否则proc_close可能永远卡在waitpid - 别用
shell_exec("curl ...")——参数拼接易被注入,且无法获取HTTP状态码,只能靠proc_get_status查退出码,而curl的退出码和HTTP状态码不是一一对应
超时和错误怎么真正兜住
很多人只设CURLOPT_TIMEOUT,但网络中断、DNS失败、SSL握手卡死,这个参数根本不起作用——它只管整个请求耗时,不管中间哪一步挂了。
实操建议:
- 必须组合使用:
CURLOPT_CONNECTTIMEOUT_MS(建连超时,建议2000)、CURLOPT_TIMEOUT_MS(总耗时,建议5000)、CURLOPT_LOW_SPEED_LIMIT+CURLOPT_LOW_SPEED_TIME(防假死,比如10字节/秒持续30秒就断) - 检查
curl_errno($ch)比检查curl_getinfo($ch, CURLINFO_HTTP_CODE)更早、更准——比如CURLE_OPERATION_TIMEDOUT(7)或CURLE_COULDNT_RESOLVE_HOST(6)根本不会走到HTTP响应阶段 - 子进程异常退出时,
pcntl_waitpid返回的$status要用pcntl_wexitstatus和pcntl_wtermsig分别判断是正常退出还是被SIGKILL干掉,后者大概率是OOM Killer动的手
真正的难点不在发请求,而在怎么让100个并发请求里,每个都带着自己的超时策略、错误分类、重试逻辑、日志上下文跑完——这些细节堆起来,才是压垮脚本的最后一根稻草。











