这个包可以轻松地在 PHP 中实现并发运行代码。在底层,并发是通过将主 PHP 进程分叉(fork)到一个或多个子任务来实现的。
在这个例子中,我们将调用一个假想的慢速 API,三个闭包会同时运行:
use Spatie\Fork\Fork;
$results = Fork::new()
->run(
fn () => (new Api)->fetchData(userId: 1),
fn () => (new Api)->fetchData(userId: 2),
fn () => (new Api)->fetchData(userId: 3),
);
$results[0]; // 用户 1 的获取数据
$results[1]; // 用户 2 的获取数据
$results[2]; // 用户 3 的获取数据
通过 Composer 安装:
composer require spatie/fork
您可以向 run 方法传递任意数量的闭包,它们将并发执行。run 函数会返回一个包含所有执行结果的数组。
use Spatie\Fork\Fork;
$results = Fork::new()
->run(
function () {
sleep(1);
return'来自任务 1 的结果';
},
function () {
sleep(1);
return'来自任务 2 的结果';
},
function () {
sleep(1);
return'来自任务 3 的结果';
},
);
// 此代码会在 1 秒后执行
$results[0]; // 包含 '来自任务 1 的结果'
$results[1]; // 包含 '来自任务 2 的结果'
$results[2]; // 包含 '来自任务 3 的结果'
如果您需要在传递给 run 的每个可调用函数之前或之后执行一些代码,可以使用 before 或 after 方法。这些可调用函数会在子进程中、对应闭包执行前或执行后运行。
以下示例中,我们使用 Laravel 的 Eloquent 模型从数据库获取值。为了让子任务能使用数据库,必须重新连接数据库。传递给 before 的闭包会在为 run 创建的每个子任务中运行。
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Spatie\Fork\Fork;
Fork::new()
->before(fn () => DB::connection('mysql')->reconnect())
->run(
fn () => User::find(1)->someLongRunningFunction(),
fn () => User::find(2)->someLongRunningFunction(),
);
如果您需要在子任务中执行完可调用函数后进行清理,可以使用 after 方法。
如果您希望 before 或 after 中的可调用函数在父任务中运行,请将其传递给 parent 参数。
use App\Models\User;
useIlluminate\Support\Facades\DB;
useSpatie\Fork\Fork;
Fork::new()
->before(
parent: fn() => echo'这在父任务中运行'
)
->run(
fn () => User::find(1)->someLongRunningFunction(),
fn () => User::find(2)->someLongRunningFunction(),
);
您也可以分别传递在子任务和父任务中运行的不同闭包:
use Spatie\Fork\Fork;
Fork::new()
->before(
child: fn() => echo '这在子任务中运行',
parent: fn() => echo '这在父任务中运行',
)
->run(
fn () => User::find(1)->someLongRunningFunction(),
fn () => User::find(2)->someLongRunningFunction(),
);
所有输出数据都会收集到一个数组中,并在所有子任务完成后立即可用。在下面的例子中,$results 将包含三个项:
$results = Fork::new()
->run(
fn () => (new Api)->fetchData(userId: 1),
fn () => (new Api)->fetchData(userId: 2),
fn () => (new Api)->fetchData(userId: 3),
);
输出数据也可以在 after 回调中使用(每个子任务完成时就会调用,而非全部结束后):
$results = Fork::new()
->after(
child: fn (int $i) => echo $i, // 依次输出 1, 2, 3
parent: fn (int $i) => echo $i, // 依次输出 1, 2, 3
)
->run(
fn () => 1,
fn () => 2,
fn () => 3,
);
子任务的返回值使用 PHP 内置的 serialize 方法进行序列化。这意味着您可以返回任何 PHP 中正常可序列化的内容,包括对象:
$result = Fork::new()
->run(
fn () => new DateTime('2021-01-01'),
fn () => new DateTime('2021-01-02'),
);
默认情况下,所有可调用函数都会并行运行。您也可以设置最大并发进程数:
$results = Fork::new()
->concurrent(2) // 最多同时运行 2 个进程
->run(
fn () => 1,
fn () => 2,
fn () => 3,
);
在这种情况下,前两个函数会立即运行,当其中一个完成后,第三个函数才会开始执行。
这个包特别适合 Laravel Artisan 命令、CLI 脚本中需要并行处理耗时任务的场景(如批量 API 调用、数据处理等)。