当前位置:首页 > 文章列表 > 文章 > php教程 > PHP curl_multi 怎样并发请求并读取完成队列

PHP curl_multi 怎样并发请求并读取完成队列

来源:17golang原创 2026-11-02 10:30:26 0浏览 收藏

我第一次把一批接口改成并发请求时,最容易混淆的不是“怎样同时发出去”,而是“到底哪个请求已经完成”。PHP 的 curl_multi 需要把多个单请求句柄交给同一个 multi handle,再由 curl_multi_exec() 推进传输;真正的完成通知,则要通过 curl_multi_info_read() 一条条取出。

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

记住两个状态:still_running 说明还有多少句柄处于活动状态,curl_multi_info_read() 说明当前完成消息队列里还有什么。前者决定是否继续驱动,后者决定读取哪个响应和错误。

curl_multi 到底解决了什么问题

单个 curl_exec() 适合一次请求一次处理;当多个 HTTP 请求互不依赖时,把它们全部加入 CurlMultiHandle,可以让 cURL 统一管理这些连接。这里的“并发”不等于把 PHP 代码复制成多个线程,而是由 cURL multi 接口同时推进多个传输。

我现在会先把对象关系画清楚:curl_multi_init() 创建容器;每个地址通过 curl_init() 得到自己的 CurlHandle;curl_multi_add_handle() 把单个句柄登记到容器;curl_multi_exec() 负责推进。只有设置了 CURLOPT_RETURNTRANSFER,后面才适合用 curl_multi_getcontent() 取回响应体。

CurlMultiHandle、CurlHandle 与 curl_multi_exec 的静态关系说明图
图1:PHP curl_multi 多句柄与单请求句柄的静态关系说明图,不是截图,也不是本机运行证据。

先为每个地址准备自己的 CurlHandle

不要只保存 URL 字符串。完成消息返回的是具体句柄,所以我通常会同时保存句柄与 URL 的映射,这样消息到来时能知道它对应哪一个业务请求。下面的示例故意把请求配置、映射和资源清理放在一起,便于改成自己的业务封装。

 'https://api.example.test/profile',
    'orders'  => 'https://api.example.test/orders',
    'quota'   => 'https://api.example.test/quota',
];

$multi = curl_multi_init();
$requests = [];

foreach ($urls as $name => $url) {
    // RETURNTRANSFER 让响应体留在句柄中,稍后交给 getcontent 读取。
    $handle = curl_init($url);
    if ($handle === false) {
        throw new RuntimeException("无法创建请求句柄:{$name}");
    }

    curl_setopt_array($handle, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 3,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_HTTPHEADER => ['Accept: application/json'],
    ]);

    // 用对象 ID 做本地映射键;完成消息会带回同一个 CurlHandle。
    $requests[spl_object_id($handle)] = [
        'name' => $name,
        'url' => $url,
        'handle' => $handle,
    ];

    $addResult = curl_multi_add_handle($multi, $handle);
    if ($addResult !== CURLM_OK) {
        curl_close($handle);
        throw new RuntimeException("加入 multi 失败:{$name}");
    }
}

这里没有把超时当成 HTTP 错误处理。连接超时、DNS 错误等属于 cURL 传输结果;即使传输成功,服务器返回 404 或 500 也仍然需要通过 curl_getinfo() 查看 HTTP 状态。

用 still_running 驱动,用 select 避免空转

调用 curl_multi_exec() 后,它会通过引用更新 $running。这个数字只适合回答“还有没有活动中的传输”,不告诉你哪一个已经完成,也不等于完成队列的长度。请求仍在进行时,可以用 curl_multi_select() 等待读写事件;如果它返回 -1,短暂休眠可以避免循环持续占用 CPU。

 0) {
        $ready = curl_multi_select($multi, 1.0);
        if ($ready === -1) {
            usleep(1000); // cURL 没有给出可等待的描述符时让出一点 CPU。
        }
    }
} while ($running > 0);

实际项目里,我更常把完成消息的读取放在每次 curl_multi_exec() 之后,而不是等 $running 变成 0 才一次性读取。这样可以边完成边消费,也能及时释放已经结束的句柄。

完成队列要用 curl_multi_info_read 排空

curl_multi_info_read() 返回一条完成消息,读完后要继续调用,直到返回 false,这表示当前时刻的消息已经取空。消息中的 handle 指向完成的请求,result 是 cURL 传输结果;当 msg 为 CURLMSG_DONE 时,才进入响应体和 HTTP 状态处理。

curl_multi_exec、still_running 与 curl_multi_info_read 完成消息的静态关系说明图
图2:PHP curl_multi 完成消息与结果读取的静态关系说明图,不是截图,也不是运行结果。
 'unknown', 'url' => ''];
        $httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);

        if ($info['result'] === CURLE_OK) {
            // 传输成功不代表 HTTP 一定是 2xx,状态码要单独判断。
            $results[$meta['name']] = [
                'url' => $meta['url'],
                'http_code' => $httpCode,
                'body' => curl_multi_getcontent($handle),
            ];
        } else {
            // result 是 cURL 传输错误,例如超时、解析失败或 TLS 失败。
            $results[$meta['name']] = [
                'url' => $meta['url'],
                'http_code' => $httpCode,
                'error' => curl_strerror($info['result']),
            ];
        }

        // 先读取需要的数据,再移除并关闭这个已经完成的句柄。
        curl_multi_remove_handle($multi, $handle);
        curl_close($handle);
        unset($requests[$key]);
    }

    if ($running > 0) {
        $ready = curl_multi_select($multi, 1.0);
        if ($ready === -1) {
            usleep(1000); // 处理 select 的异常返回,避免忙等。
        }
    }
} while ($running > 0);

curl_multi_close($multi);

如果你发现“请求都结束了但结果数组为空”,优先检查是否漏掉了 curl_multi_info_read() 循环,或者是否没有设置 CURLOPT_RETURNTRANSFER。如果只判断 $running,你只能知道活动句柄变少了,不能拿到已完成句柄的业务结果。

我会重点检查的三个边界

现象应该看什么常见误区
整个 multi 调用失败curl_multi_exec() 的返回值把它当成某一个 URL 的 HTTP 状态
某个请求传输失败curl_multi_info_read() 返回消息里的 result只看 curl_getinfo() 的状态码
服务器返回 404/500curl_getinfo($handle, CURLINFO_HTTP_CODE)把非 2xx 当成 cURL 传输异常
循环 CPU 占用异常curl_multi_select() 的等待逻辑无条件高速调用 curl_multi_exec()

PHP 8 中相关函数参数和返回值使用 CurlHandle、CurlMultiHandle 对象;较早版本使用 resource。代码结构仍然是“初始化、加入、驱动、读取消息、移除、关闭”,但项目若要兼容旧 PHP,应该把实际版本和 cURL 扩展能力纳入部署检查。

适合什么时候用 curl_multi

它适合一批彼此独立、数量可控、需要一次请求周期内合并处理的 HTTP 调用,例如读取多个服务的概览数据。它不自动提供业务限流、重试策略、熔断、优先级或任务持久化;请求数量很大时,我会在外层分批加入句柄,并记录每个请求的超时和失败原因。

我自己的判断是:如果只是连续调用两三个接口,单句柄代码可能更直观;当等待时间明显来自多个相互独立的网络请求时,curl_multi 才真正值得引入。关键不是追求“越多句柄越快”,而是让完成消息、HTTP 状态和资源清理都保持可追踪。

相关问题

  • curl_multi_info_read() 返回 false 是错误吗? 不是,它表示当前完成消息已经读空;只要 $running 仍大于 0,就继续等待并驱动。
  • curl_multi_exec() 返回 CURLM_OK 就代表所有请求成功吗? 不代表。它只说明 multi 栈本身没有报错,单请求结果仍要看消息里的 result。
  • 为什么拿不到响应体? 检查每个句柄是否设置 CURLOPT_RETURNTRANSFER => true,并在移除句柄前调用 curl_multi_getcontent()。
  • 是否应该把 HTTP 500 当作 cURL 错误? 不应该。传输成功但 HTTP 状态异常时,应把状态码作为业务失败单独处理。

参考入口:https://www.php.net/manual/en/function.curl-multi-exec.php、https://www.php.net/manual/en/function.curl-multi-info-read.php、https://www.php.net/manual/en/function.curl-multi-add-handle.php。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
PHP Composer 平台配置怎样锁定运行时版本PHP Composer 平台配置怎样锁定运行时版本
上一篇
PHP Composer 平台配置怎样锁定运行时版本
PHP readonly 类怎样避免配置对象被意外修改
下一篇
PHP readonly 类怎样避免配置对象被意外修改
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    842次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    842次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    921次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    897次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    725次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码