在您编写的任何程序中,有两种执行任务的方法。这些任务要么依次执行,要么并行执行,无需等待前一个任务完成。前者的任务执行方式称为同步执行,后者称为异步执行。
有时,任务或指令需要按顺序执行,例如从抓取的网页中提取标题时。网页的抓取必须在任何提取发生之前进行。
但是,在某些情况下,您可能希望异步执行任务。例如,假设您要从 20 个不同的网页中提取标题。您可以并行运行多个请求,而无需等待第一个请求完成,而不是等待一个页面的抓取和提取完成后再继续下一个。
在本教程中,我们将学习如何使用Spatie 异步库在 php 中并行执行多个任务。
在 Windows 上设置 Spatie
Spatie 异步库实际上为 PHP 的PCNTL 扩展提供了一个易于使用的包装器。但是,PCNTL 扩展不适用于 Windows。这意味着您只能在 UNIX 环境中使用该库。
幸运的是,只需使用 WSL在 Windows 上安装 Linux即可轻松解决此问题。别担心——这听起来比实际上要复杂得多。您需要做的就是在管理员模式下运行 PowerShell 或 Windows 命令提示符后执行以下命令。
wsl --install
上面的命令将安装 Ubuntu 作为默认的 Linux 发行版,这对我们的目的来说很好。安装过程完成后,您可以从“开始”菜单打开 Ubuntu。提供用户名和密码。这个新的 Linux 帐户将被视为管理员,并允许您运行sudo管理命令。
如果尚未安装Visual Studio Code,我建议您安装它。在 Visual Studio Code 中,您还应该考虑安装 Remote WSL 扩展,以便您轻松编辑位于 WSL 或 Windows 文件系统中的文件,而无需担心任何跨平台问题。
现在,您应该在 Ubuntu 环境中运行以下命令。
code .
这将安装一个 shim 服务器,使 WSL 和 VSCode 可以相互通信。您还需要安装 Composer,以便您更轻松地安装和更新库。
设置好开发环境后,您可以在 Ubuntu 中通过运行以下命令创建任务目录:
mkdir tasks
现在运行更改目录命令进入任务目录。
cd tasks
在tasks目录中,我们最终可以通过运行以下命令来安装spatie/asyc包:
composer require spatie/async
检查安装是否成功
假设您在未安装 PHP PCNTL 扩展的环境中使用此库。在这种情况下,库将自动同步执行代码作为后备。
检查我们是否在支持异步进程的环境中运行代码的一种方法是使用isSupported()库中的方法,该方法返回一个布尔值。true如果代码可以异步运行,则返回值。
在任务目录中创建一个名为test.php的文件,并将以下代码添加到其中。
<?php require_once('vendor/autoload.php'); use Spatie\Async\Pool; $pool = Pool::create(); if($pool->isSupported()) { echo 'We can run asynchronous code!'; } else { echo 'Something is wrong!'; } ?
如果一切设置正确,您还应该得到我们可以运行异步代码!作为运行上述代码时的输出。
并行执行请求
该库使用该symfony/process组件来创建和管理不同的子进程。由于该库可以创建多个子进程,因此它能够并行执行 PHP 脚本。这使您可以并行运行多个独立的同步任务,并显着减少完成所有任务所需的时间。
并行运行进程时需要注意的一件事是不要一次生成大量进程。这可能会导致您的应用程序意外崩溃。
幸运的是,spatie/async使用类的一些辅助方法来解决这个问题Pool。该add()方法可以通过以最佳方式调度和运行它们来处理任意数量的进程。
不同的过程将需要不同的时间来完成。理想的做法是等待池中的所有进程完成后再继续,而不会意外杀死任何子进程。该任务由wait()方法处理。
假设您想在特定子进程完成并触发成功事件后执行一些其他代码。您可以在该功能的帮助下做到这一点then()。
我们现在将编写一些代码来创建十个不同的文本文件。为了比较,我们将首先编写代码以使其同步运行,然后将其更新为异步运行。
这是同步代码:
<?php for($i = 1; $i <= 10; $i++) { $file_name = "file_$i.txt"; $content = bin2hex(random_bytes(2048)); file_put_contents($file_name, $content); echo "Generated file: $file_name".PHP_EOL; } ?
上面的代码给出了以下输出:
Generated file: file_1.txt Generated file: file_2.txt Generated file: file_3.txt Generated file: file_4.txt Generated file: file_5.txt Generated file: file_6.txt Generated file: file_7.txt Generated file: file_8.txt Generated file: file_9.txt Generated file: file_10.tx
每个文件的内容只是一个4096 字节长的随机十六进制字符串。这是一个例子:
841bda21ae704ecd05ad64ccb4fb029c6c6e8bc590eda828e2080d9f9f842c1f39883fd8e837325655184219ed92d3a9ca356b96c4a0edeb751d7270f8c1b3b949975ab9786289870a3f3cb7501..... and so on
我们现在将重写代码,使其异步运行。这是它的样子:
<?php require_once('vendor/autoload.php'); use Spatie\Async\Pool; $pool = Pool::create(); for($i = 1; $i <= 10; $i++) { $pool->add(function() use ($i) { $file_name = "file_$i.txt"; $content = bin2hex(random_bytes(2048)); file_put_contents($file_name, $content); return $file_name; })->then(function ($file_name) { echo "Generated file: $file_name".PHP_EOL; }); } $pool->wait(); ?
上面的代码将生成以下输出:
Generated file: file_5.txt Generated file: file_6.txt Generated file: file_1.txt Generated file: file_9.txt Generated file: file_8.txt Generated file: file_2.txt Generated file: file_4.txt Generated file: file_3.txt Generated file: file_10.txt Generated file: file_7.tx
如您所见,当我们异步执行代码时,文件并没有按顺序生成。换句话说,file_5.txt不必等待file_1.txt生成。then()一旦触发成功事件,我们就会在函数内输出文件的名称。
add()使用and方法的另一种替代wait()方法是使用函数async()and await()。使用这些函数,我们的代码将如下所示:
<?php require_once('vendor/autoload.php'); use Spatie\Async\Pool; $pool = Pool::create(); for($i = 1; $i <= 10; $i++) { $pool[] = async(function() use ($i) { $file_name = "file_$i.txt"; $content = bin2hex(random_bytes(2048)); file_put_contents($file_name, $content); return $file_name; })->then(function ($file_name) { echo "Generated file: $file_name".PHP_EOL; }); } await($pool); ?>
使用事件监听器
在上一节中,我们创建了很多子进程并将它们添加到我们的Pool类中以异步执行。池中的不同进程彼此独立运行。这意味着我们需要一些方法来确定特定任务何时完成。成功执行任务时触发成功事件。此时,我们可以使用该then()函数自由地执行一些其他代码。
但是,流程并不总是能够成功执行。在某些情况下,他们要么失败,要么超时而无法完成手头的任务。您可以通过为函数提供回调来处理异常,catch()并通过为函数提供回调来处理超时timeout()。
让我们一起使用所有这些概念来编写一些测试Collatz 猜想的代码。这个猜想告诉我们,如果一个偶数返回它的一半作为下一项,而一个奇数返回 3 倍自身 + 1 作为下一项,你最终将得到 1。例如,14 的序列将是 14 > 7 > 22 > 11 > 34 > 17 > 52 > 26 > 13 > 40 > 20 > 10 > 5 > 16 > 8 > 4 > 2 > 1。
我们将在我们的代码中运行十次迭代,每次通过都会选择一个随机数。由于猜想只处理正数,所以只要随机数小于 1,我们就会抛出异常。下面是我们的代码:
<?php require_once('vendor/autoload.php'); use Spatie\Async\Pool; $pool = Pool::create(); for($i = 0; $i < 10; $i++) { $pool->add(function() use ($i) { $orig_num = $num = mt_rand(-10000, 100000); if($i == 0) { $orig_num = $num = 75128138247; } $count = 0; if($num < 1) { throw new Exception("Conjecture not applicable on $orig_num."); } while($num != 1) { if($num%2 == 0) { $num /= 2; } else { $num = 3*$num + 1; } $count++; } return [$orig_num, $count]; })->then(function ($output) { echo "".$output[0]." reduced to 1 in ". $output[1] ." steps.". PHP_EOL; })->catch(function($e) { echo "Caught Exception ". $e->getMessage() . PHP_EOL; })->timeout(function() { echo "Process took too long \n"; }); } ?> $pool->wait()
由于猜想表明每个正数最终都会变为 1,因此我们的代码最终将退出while循环并返回原始数字以及达到 1 所需的迭代次数。如果数字小于 1,我们也会抛出异常,因为猜想只适用于正数。
尝试运行代码几次,你肯定会遇到异常。这是我的输出:
47443 reduced to 1 in 75 steps. 75128138247 reduced to 1 in 1228 steps. 44961 reduced to 1 in 62 steps. 28545 reduced to 1 in 59 steps. 53756 reduced to 1 in 246 steps. Caught Exception Conjecture not applicable on -8059. 39324 reduced to 1 in 106 steps. Caught Exception Conjecture not applicable on -7991. 97972 reduced to 1 in 190 steps. 71809 reduced to 1 in 94 steps
您可能已经注意到,我们在循环的第一次迭代中传递了一个非常大的数字。它花了 1228 步才到达 1。但是,它仍然足够快,可以逃脱超时条件。
池配置选项
假设您正在做某事,您想要在特定时间内取得结果,或者您想要放弃手头的任务。例如,您只想计算完成时间少于 0.01 秒的步骤。您如何执行该约束?
这是池配置选项证明有用的地方。有四种有用的方法可供您使用。
concurrency()确定可以同时运行的最大进程数。默认情况下设置为 20。
timeout()确定进程在超时之前在池中运行多长时间。默认值为 300 秒。
sleepTime()确定循环检查进程状态的频率。默认值为 50000 微秒。
autoload()指定不同子进程应该使用的自动加载器。
在我们的例子中,我们将超时值设置为 0.01 秒。我们需要做的就是在创建循环之前添加以下行。
$pool->timeout(0.01);
如果您使用此修改重新运行上一节中的代码,您会注意到一些数字现在在达到值 1 之前超时。在现实生活中,您可以使用此选项来结束进程,例如读取 a 的内容非常大的文件,如果它需要太长时间。
最后的想法
我们在本教程中讨论了很多概念。我们首先学习了并行处理和异步运行代码如何帮助我们更快地完成工作。之后,我们学习了如何在 Windows 中设置 WSL 以使用异步库。设置成功后,我们看到了如何使用并行处理创建多个文件。
最后,我们了解了不同的事件监听器以及如何使用池配置选项来确保我们的进程在某些约束下运行。为了练习,您应该尝试弄清楚如何并行运行多个进程,以便在 PHP 中快速编辑图像。
发表评论