spatie/async不是Laravel官方组件,也不提供队列功能,而是通过pcntl_fork直接创建子进程执行PHP代码,每个子进程无Laravel应用上下文,故Log::error()等Facade会报“A facade root has not been set”;需手动初始化应用或改用原生日志;其concurrency设置仅限制最大子进程数,实际并发受PCNTL支持、内存、I/O及调度开销制约;适用于单次请求内无状态、可丢弃的并行计算或轻量I/O任务,与queue:work的持久化、可重试、分布式队列场景本质不同;闭包use变量为值传递,不可传资源对象或依赖动态更新的变量,错误需显式catch否则静默失败。

spatie/async 不是 Laravel 官方组件,也不是队列系统,它直接 fork 子进程执行 PHP 代码——这意味着你不能在其中直接使用 Laravel 的 Facade、容器或数据库连接,除非显式初始化。
为什么 Log::error() 在 spatie/async 里会报 “A facade root has not been set”
因为每个子进程从零启动,不加载 Laravel 应用上下文。Facade(如 Log、DB、Cache)依赖 bootstrap/app.php 初始化的容器实例,而子进程默认只运行你的闭包或 Task 类的 run() 方法,没有自动加载框架环境。
- 错误现象:
Exception: A facade root has not been set - 根本原因:子进程未调用
require_once __DIR__.'/../../bootstrap/app.php',也未手动绑定容器 - 可行解法(二选一):
- 在
Task::configure()中手动引入并启动 Laravel 应用(需确保路径正确、且不重复启动 HTTP 内核) - 改用原生 PHP 日志(
error_log())、文件写入或传参方式把结果带回主进程再记录
- 在
- 不推荐做法:在闭包里直接
requirevendor/autoload.php后再new \Illuminate\Foundation\Application—— 容易引发内存泄漏或类冲突
Pool::create()->concurrency(10) 真的能跑满 10 个 CPU 核吗?
不能。并发数 ≠ CPU 利用率,更不等于实际并行任务数。它只是 spatie/async 允许同时存在的子进程上限,真实并发受制于:
-
pcntl_fork()可用性:必须启用pcntl扩展,且 Web SAPI(如 Apache、PHP-FPM)下默认禁用,仅 CLI 模式安全可用 - 系统资源:每个子进程独占内存,若单任务占 20MB,
concurrency=20就可能吃掉 400MB+ 内存 - I/O 阻塞:如果任务大量读文件或发 HTTP 请求,CPU 实际空闲,再多进程也无提速效果
- PHP 进程管理开销:频繁 fork + waitpid 带来调度成本,
concurrency超过 8~12 后收益常递减
建议值:本地开发设 concurrency(4),生产 CLI 脚本可试 8,超过需配合 timeout() 和 sleepTime() 观察稳定性。
立即学习“PHP免费学习笔记(深入)”;
和 php artisan queue:work 并发比,spatie/async 适合什么场景?
两者解决的问题不同,别混用:
-
queue:work是「任务持久化 + 多 worker 消费」,适合需要失败重试、延迟执行、跨服务器分发、高可靠性保障的场景(如发邮件、支付回调处理) -
spatie/async是「瞬时多进程并行」,适合单次请求内、无状态、可丢弃、纯计算或轻量 I/O 的任务(如批量图像缩略图生成、数组聚合、API 数据预抓取) - 关键区别:
- 队列任务失败后进
failed_jobs表,可重试;spatie/async报错即丢弃,靠catch()捕获但无法自动重放 - 队列 worker 可长期运行;
spatie/async所有子进程随$pool->wait()结束而销毁 - 队列天然支持 Redis/DB/Beanstalkd 等驱动;
spatie/async仅限当前机器,无分布式能力
- 队列任务失败后进
用 async() 写闭包时,变量捕获要注意什么?
PHP 闭包 use ($var) 是值传递,不是引用。常见陷阱:
- 对象未序列化:若
$var是 PDO 实例、Redis 连接等资源型对象,use后在子进程中会失效,报Serialization of 'PDO' is not allowed - 大数组内存复制:闭包捕获 10MB 数组,每个子进程都复制一份,
concurrency=5就多占 50MB - 动态变量不可见:闭包外修改了
$counter,但闭包内use ($counter)拿到的是定义时的快照值 - 安全建议:
- 只
use简单类型(int/string/array)或可序列化的 DTO 对象 - 避免传
$this或 Laravel Service Container 实例 - 如需共享状态,改用主进程收集结果,而非子进程间通信
- 只
真正容易被忽略的点:子进程的错误不会自动抛到主进程,必须显式 catch() 或检查 $pool->getFailedTasks(),否则 silent fail。



















