Composer本身不支持并发执行,run-script严格串行;真并发需在单个PHP脚本内用Guzzle Promises实现异步I/O,配合超时控制与连接池管理。

Composer 本身不支持自动检测并发执行,也不存在“任务调度”能力——所有所谓“并发”,必须由外部脚本或系统级工具实现。
composer run-script 是串行的,别指望它自动并行
你在 composer.json 里写多少个 script,composer run-script a b c 都是挨个等前一个退出才启动下一个。Composer 不 fork 进程、不管理句柄、不轮询状态,它只是按顺序调用 shell 命令。
- 错误退出(比如
fetch.php报错)会直接中断后续脚本,除非你手动加&&或|| - 想“同时跑多个 API 请求”,不能靠多条 script 拆分,得把并发逻辑写进单个 PHP 脚本里
- 别在 scripts 字段里塞
php a.php & php b.php & wait—— 这种写法在 CI 环境下极易因信号丢失或进程回收失败导致悬空子进程
真并发只能靠 Guzzle Promises 在 PHP 脚本内实现
唯一可控、可落地的并发方式,是让一个 script 调起的 PHP 文件自己做非阻塞 I/O。Guzzle Promises 是目前最轻量、兼容性最好、错误链最清晰的选择。
- 安装:
composer require guzzlehttp/promises - 关键不是
getAsync(),而是用Promise\settle($promises)->wait()或Promise\all($promises)->wait()统一收口 - 必须设超时:
$client->getAsync('...', ['timeout' => 5]),否则一个慢请求拖垮全部 - 别用
curl_multi手动管理——PHP CLI 下容易漏curl_close(),且无法继承 Guzzle 的重试、中间件、异常分类机制
并发 ≠ 多线程,CLI 下仍是单进程轮询
PHP CLI 默认没有线程,Guzzle Promises 的并发本质是基于 stream_select() 或 select() 的事件循环,靠操作系统通知 I/O 就绪,不是开 N 个 CPU 核心。
- 这意味着:高并发请求不会提升 CPU 使用率,但会显著增加内存占用(每个 Promise 对应一个 resource 句柄)
- 若并发数设到 50+,又没控制好连接池(
['handler' => \GuzzleHttp\HandlerStack::create()]),可能触发文件描述符耗尽(Too many open files) - 生产环境建议上限设为 10–20,并配合
Promise\each_limit()分批处理
CI/CD 中“伪并发”的常见踩坑点
有人在 GitHub Actions 里用 matrix 策略跑多个 composer run-script job,误以为这是 Composer 并发——其实只是系统级并行,和 Composer 无关,反而容易因共享缓存或 vendor 冲突出问题。
- 多个 job 同时执行
composer install,但共用同一份composer.lock和缓存目录,可能触发文件锁或写入竞争 - job A 正在写
vendor/autoload.php,job B 就去 require 它,导致Class not found或解析失败 - 真正安全的做法:每个 job 独立工作目录 + 独立缓存 key,或改用单 job +
Promise\each_limit()控制节奏
复杂点在于:并发逻辑一旦下沉到脚本里,错误传播就变弱了——Promise\settle() 返回的是混合成功/失败的状态数组,不抛异常;你得自己遍历 $results 判断哪些失败、是否要重试、要不要提前中止。这比 shell 串行难 debug,但更可控。











