PHP curl处理 curl_multi_select 返回零的忙等的实现方法
在 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 | 等待调用失败 | 记录上下文,按项目策略重试或终止 |

忙等通常表现为 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()。这样,事件等待层不会把已完成句柄继续留在轮询集合里。

排查时可记录四个指标:调用 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 层返回值。
LibTV能做AI视频创作吗?功能范围和适用场景
- 上一篇
- LibTV能做AI视频创作吗?功能范围和适用场景
- 下一篇
- Go maps 包复制 map 并隔离写入的实现方式
-
- 文章 · php教程 | 2小时前 | PHP · 时区 · 时区转换 DateTimeZone DateTimeImmutable setTimezone PHP DateTime
- PHP DateTime转换时区而不改变瞬时时刻的实现方法
- 261浏览 收藏
-
- 文章 · php教程 | 3小时前 |
- PHP Intl按地区格式化金额与小数的实现方法
- 122浏览 收藏
-
- 文章 · php教程 | 5小时前 |
- PHP OPcache区分 CLI 与 FPM 的缓存进程的实现方法
- 304浏览 收藏
-
- 文章 · php教程 | 4天前 |
- PHP attributes用 Reflection 读取自定义属性的实现方法
- 398浏览 收藏
-
- 文章 · php教程 | 4天前 | 协程 · PHP · 异步编程 · Fiber::suspend Fiber::resume PHP Fiber Fiber::getReturn 协程值传递 Fiber 生命周期
- PHP Fiber设计 suspend 与 resume 的值传递的实现方法
- 366浏览 收藏
-
- 文章 · php教程 | 4天前 | HTTP · php教程 · stream_get_meta_data PHP Stream stream_get_wrappers HTTP wrapper 文件流识别
- PHP stream识别文件与 HTTP wrapper 的差异的实现方法
- 217浏览 收藏
-
- 文章 · php教程 | 4天前 | PHP · php Curl curl_multi_exec curl_multi_select
- PHP curl_multi_select 返回 0 时如何继续轮询
- 132浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 122次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 196次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 140次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 114次使用
-
- CMMLU
- 深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
- 101次使用
-
- PHP JSON_THROW_ON_ERROR 抛错后怎么保留原始字段位置
- 2026-09-09 501浏览
-
- PHP 8.5 array_last() 怎么处理空数组:从 null 结果到兼容旧版本的 Polyfill
- 2026-08-16 501浏览
-
- 宝塔配置Ruby环境:RVM+Nginx反代教程
- 2026-05-29 501浏览
-
- unset函数作用范围详解
- 2026-05-29 501浏览
-
- VS Code配置Xdebug教程:PHP调试技巧全解析
- 2026-05-13 501浏览

