登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

PHP curl处理 curl_multi_select 返回零的忙等的实现方法

来源:17golang原创

时间:2026-09-20 03:19:23 191浏览 收藏

在 PHP 的多请求循环里,curl_multi_select() 返回 0 不代表请求失败,它只说明这一次等待没有发现活动描述符。真正容易出问题的是马上再次调用 curl_multi_exec(),循环会变成无间隔的忙等。处理方式是:继续保留运行中的句柄,遇到 0 时短暂让出 CPU,再回到执行与完成消息读取;只有返回 -1 才按 select 失败处理。

官方地址:https://www.php.net/

要点速览
  • curl_multi_select() 返回正数表示有描述符活动,返回 0 表示本轮没有活动,返回 -1 表示底层等待失败。
  • 返回 0 时用一个很短的 usleep() 让出 CPU,然后继续调用 curl_multi_exec(),不要把 0 当成完成。
  • 请求完成后通过 curl_multi_info_read() 读取单个结果,移除句柄并关闭资源;multi 栈返回 OK 也不等于每个请求都成功。

先把 multi 请求循环跑起来

multi API 的核心不是一次性执行所有 URL,而是反复推进一组句柄。$running 表示仍在处理的句柄数量;只要它大于 0,就继续执行。下面的示例使用两个公共地址作为演示目标,实际项目应替换成自己的接口,并为每个句柄保存业务标识。

 true,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_TIMEOUT => 15,
    ]);
    curl_multi_add_handle($multi, $handle);
    $handles[(int) $handle] = $handle;
}

$running = 0;
do {
    // 推进所有可继续读写的句柄;返回值只表示 multi 栈层面的状态。
    $multiCode = curl_multi_exec($multi, $running);
    if ($multiCode !== CURLM_OK) {
        throw new RuntimeException(curl_multi_strerror($multiCode));
    }

    // 先处理本轮已经完成的请求,再决定是否等待下一次网络活动。
    while (($info = curl_multi_info_read($multi)) !== false) {
        $done = $info['handle'];
        $status = $info['result'];
        if ($status !== CURLE_OK) {
            error_log(curl_error($done));
        }
        curl_multi_remove_handle($multi, $done);
        curl_close($done);
    }

    if ($running > 0) {
        // 只等待仍在运行的请求;返回值分支见下一节。
        $selected = curl_multi_select($multi, 1.0);
        if ($selected === 0) {
            // 没有活动描述符时让出 1 毫秒,避免 while 循环占满 CPU。
            usleep(1000);
        } elseif ($selected === -1) {
            // -1 是等待失败,不应与“本轮无活动”的 0 混为一谈。
            usleep(1000);
        }
    }
} while ($running > 0);

curl_multi_close($multi);
?>

这段代码把“推进传输”“读取完成消息”和“等待下一次活动”分开,后续排查时可以分别计数。若项目需要保留响应正文,应在完成分支调用 curl_multi_getcontent($done) 后再释放句柄。

curl_multi_select 返回值要按三种状态处理

官方手册把返回值定义得很清楚:成功时返回活动描述符数量,可能为 0;底层 select() 失败时返回 -1。因此,0 的正确语义是“现在没有可读写事件”,不是“所有请求已完成”,也不是“发生错误”。

返回值含义循环动作
大于 0发现活动描述符立即回到 curl_multi_exec()
等于 0本轮没有活动短暂 usleep() 后重试
等于 -1等待调用失败记录上下文,按项目策略重试或终止
PHP curl_multi_exec 与 curl_multi_select 围绕返回 0、正数和负一的事件轮询结构说明图
图1:PHP cURL 多句柄轮询的返回值分支说明图,不是运行截图。

忙等通常表现为 CPU 占用突然升高、循环次数远超请求数,而网络吞吐没有变化。不要用“把 select 超时设得极小”来掩盖问题;超时负责等待上限,返回 0 后的让出间隔负责避免空转,两个参数解决的是不同层次的问题。

返回零时怎样让出 CPU 又不拖慢请求

返回 0 后的处理没有一个适用于所有机器和网络的固定数字。可以从 usleep(1000) 开始,也就是让出约 1 毫秒,再继续执行;高并发服务应结合循环次数、单批请求耗时和进程 CPU 使用率调整。间隔过短,忙等仍可能存在;间隔过长,响应尾延迟会增加。

还有两个边界需要记住:

  • $running 仍大于 0 时才等待。它变成 0 后,先把完成消息读完,再结束循环。
  • 不要因为 curl_multi_exec() 返回 CURLM_OK 就判定所有 HTTP 请求成功;单个句柄的 $info['result']、HTTP 状态码和业务响应仍要单独检查。

如果需要统一封装,可以只把“等待”写成一个小函数,让业务代码继续关注结果:

读取完成消息并回收句柄

curl_multi_info_read() 读取的是已经完成的传输消息。完成后先取出句柄和传输结果,再读取正文或 HTTP 信息,随后调用 curl_multi_remove_handle()curl_close()。这样,事件等待层不会把已完成句柄继续留在轮询集合里。

PHP CURLMSG_DONE 完成消息经过 result、curl_multi_getcontent 到移除和关闭句柄的关系结构图
图2:完成消息到句柄回收的关系结构图,不是运行截图。

排查时可记录四个指标:调用 curl_multi_exec() 的次数、curl_multi_select() 返回 0 的次数、返回 -1 的次数,以及每个请求的最终 result。如果 0 次数很多但 CPU 已恢复,说明让出策略生效;如果 -1 持续出现,则应先查运行环境与 libcurl 行为,而不是继续加大循环频率。

相关问题

curl_multi_select 返回 0 是错误吗?

不是。它表示等待窗口内没有活动描述符,仍需回到 curl_multi_exec() 判断传输是否推进或完成。

返回 0 后可以直接 continue 吗?

可以继续轮询,但不建议无间隔 continue。短暂 usleep() 能给出让出 CPU 的机会,具体间隔应通过指标调整。

如何判断某一个请求真的失败?

读取 curl_multi_info_read() 返回的 result,并结合 curl_getinfo() 的 HTTP 状态码和业务响应判断;不能只看 multi 层返回值。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>