串行执行是PHP接口变慢的头号元凶。假设一个聚合页面需要调用三个外部接口,每个接口耗时800毫秒,如果按顺序依次请求,总耗时就是2400毫秒起步,再加上自身业务逻辑的处理时间,用户面对的可能就是一个长达数秒的白屏。这时候多线程或者说并发处理就成了最直接的优化手段。PHP本身不是原生支持多线程的语言,但通过扩展和设计模式,同样可以实现并发执行的效果。下面介绍几种在项目中验证过的方案。

curl_multi:最实用的HTTP并发请求方案
如果慢的原因是需要请求多个外部HTTP接口,那么curl_multi系列函数是首选方案,它不需要安装任何额外扩展,标准PHP环境自带。它的原理是把多个curl句柄交给一个multi句柄统一调度,底层使用IO多路复用,同时发出所有请求,谁的响应先回来就先处理谁,总耗时约等于最慢的那个请求,而不是所有请求耗时之和。
下面是一个典型的并发抓取示例,同时请求三个URL并收集结果:
<?php
function multiRequest(array $urls, int $timeout = 10): array
{
$mh = curl_multi_init();
$handles = [];
foreach ($urls as $i => $url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_multi_add_handle($mh, $ch);
$handles[$i] = $ch;
}
// 执行并发请求
do {
$status = curl_multi_exec($mh, $active);
if ($active) {
curl_multi_select($mh, 0.1); // 避免CPU空转
}
} while ($active && $status == CURLM_OK);
// 收集结果
$result = [];
foreach ($handles as $i => $ch) {
$result[$i] = curl_multi_getcontent($ch);
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
return $result;
}
$urls = [
'https://api.ipipp.com/user/info',
'https://api.ipipp.com/order/list',
'https://api.ipipp.com/goods/detail',
];
$responses = multiRequest($urls);
var_dump($responses);
这个方案的优势在于零依赖、代码可控,三个800毫秒的请求并发执行后总耗时大约只有800多毫秒,提升立竿见影。需要注意两点:一是curl_multi_select要记得调用,否则在极端情况下会导致CPU占用飙高;二是并发数量要控制,如果一次性发出上百个请求,可能触发对方的限流策略,建议用分批的方式,每批控制在10到20个。
pcntl_fork:进程级并行的重型方案
pcntl扩展提供了真正的进程创建能力,pcntl_fork调用后会产生一个子进程,父子进程可以各自执行不同的任务。它适合CPU密集型场景,比如批量图片处理、大量数据计算,这类任务瓶颈在于CPU运算而不是IO等待,curl_multi帮不上忙,就必须靠多进程把任务拆到多个CPU核心上并行执行。
<?php
$tasks = [1, 2, 3, 4];
$children = [];
foreach ($tasks as $task) {
$pid = pcntl_fork();
if ($pid == -1) {
die('fork失败');
} elseif ($pid == 0) {
// 子进程处理任务
$result = heavyCompute($task);
exit(0);
} else {
$children[] = $pid;
}
}
// 父进程等待所有子进程结束
foreach ($children as $pid) {
pcntl_waitpid($pid, $status);
}
echo "全部任务完成\n";
function heavyCompute($task)
{
sleep(2); // 模拟耗时计算
return $task * 2;
}
pcntl方案有几个硬性限制必须清楚:只能在CLI模式下运行,也就是只能用于脚本、定时任务、队列消费程序,不能用在FPM处理的Web请求里;其次子进程之间内存完全隔离,无法直接共享变量,需要通过消息队列、Redis或者数据库来交换数据。因此在Web场景下,更合理的用法是用pcntl写一个常驻的worker进程去消费任务队列,把耗时操作从HTTP请求中剥离出去。
Swoole协程:现代化的高性能选择
Swoole是PHP生态里解决并发问题最彻底的方案。它提供了协程能力,写法上看起来是同步代码,底层会自动在IO等待时切换执行其他协程,用同步的代码风格获得异步的性能。对于既要并发请求接口,又要操作数据库、Redis的项目,Swoole协程可以把所有IO操作的时间重叠起来。
<?php
Co\run(function () {
$results = [];
$wg = new Co\WaitGroup();
foreach ($urls as $i => $url) {
$wg->add();
go(function () use ($i, $url, &$results, $wg) {
$results[$i] = co_http_get($url);
$wg->done();
});
}
$wg->wait(); // 等待所有协程完成
var_dump($results);
});
Swoole的缺点在于部署成本,需要安装扩展,运维习惯也要相应调整,比如使用连接池、注意协程环境下的全局变量污染。如果是长期维护的核心服务,投入是值得的;如果只是一个小项目需要并发抓几个接口,用curl_multi就够了,没必要引入Swoole增加复杂度。
异步化改造:从根本上降低响应时间
除了并发执行,还有一条思路值得考虑:把不需要同步返回结果的任务异步化。比如用户注册后要发邮件、发短信、写日志,这些操作没必要让用户等,扔进队列立即返回,用户感知到的响应时间就只剩核心业务的部分。常见实现是把任务写入Redis列表或者使用Beanstalkd、RabbitMQ,后台用pcntl或者 Supervisor 管理的常驻进程去消费。
这种架构上的调整往往比单纯堆并发更有效。判断标准很简单:这个操作的结果用户当前这次请求需要吗?不需要就异步化,需要才考虑并发加速。实际项目中通常是组合拳:同步返回的部分用curl_multi或协程并发加速,非关键路径的任务全部进队列,这样接口响应时间可以从数秒降到几百毫秒,同时系统吞吐量也会明显提升。
方案选型建议
总结一下选型思路:请求外部接口多、只做IO等待,选curl_multi,成本最低见效最快;CPU密集型的脚本任务用pcntl_fork多进程分摊;需要长期运行的高并发服务,上Swoole协程体系;而大批量的非关键任务,交给队列异步处理。先分析慢在哪里,再对症下药,不要盲目上重型方案,很多时候一段几十行的curl_multi改造就能把响应速度提升一个量级。
PHP多线程响应速度curl_multi修改时间:2026-09-07 00:56:33