PHP的传统同步实行流程很随意马虎理解。一次只做一件事。如果查询数据库,则发送查询并等待数据库做事器的相应。一旦你有了答案,你就可以开始做下一件事。
ReactPHP和其他库已经在PHP中供应了很长一段韶光的协作多任务。然而,它们的事宜驱动特性与许多现有的接口不兼容,须要不同的思维模型。PHP 8.1内置了fibers,它供应了协作多线程。调用可以是异步的,没有promise或回调,同时仍旧许可非壅塞I/O。
每个利用协同多任务的运用程序都须要一个调度器(也称为事宜循环),这个包供应了这个调度器。Revolt是结合了React和ReactPHP的事宜循环实现的多年履历的结果。然而,它并不是一个用于编写并发PHP运用程序的成熟框架,而只是供应了必要的公共根本。不同的(强烈的)固执己见的库可以在它的根本上构建,React和ReactPHP将连续共存。

composer require revolt/event-loop
❝
把稳:此包可以作为Composer依赖项安装在PHP 8.1及更高版本上。
示例<?phprequire __DIR__ . '/vendor/autoload.php';use Revolt\EventLoop;$suspension = EventLoop::getSuspension();$repeatId = EventLoop::repeat(1, function (): void { print '++ Executing callback created by EventLoop::repeat()' . PHP_EOL;});EventLoop::delay(5, function () use ($suspension, $repeatId): void { print '++ Executing callback created by EventLoop::delay()' . PHP_EOL; EventLoop::cancel($repeatId); $suspension->resume(null); print '++ Suspension::resume() is async!' . PHP_EOL;});print '++ Suspending to event loop...' . PHP_EOL;$suspension->suspend();print '++ Script end' . PHP_EOL;
在实行上面的例子时,你该当看到这样的输出:
++ Suspending to event loop...++ Executing callback created by EventLoop::repeat()++ Executing callback created by EventLoop::repeat()++ Executing callback created by EventLoop::repeat()++ Executing callback created by EventLoop::repeat()++ Executing callback created by EventLoop::delay()++ Suspension::resume() is async!++ Script end
❝
这个输出解释了事宜循环内部发生的事情就像它自己独立的程序一样。您的脚本将不会连续通过 $suspension->suspend() 点,除非挂出发点通过 $suspension->resume() 或 $suspension->throw() 规复。
虽然一个运用程序可以而且常常险些完备在事宜循环的范围内发生,但我们也可以利用事宜循环来做一些事情,比如下面的例子,它为交互式掌握台输入施加了一个短暂的超时:
<?phprequire __DIR__ . '/vendor/autoload.php';use Revolt\EventLoop;if (\stream_set_blocking(STDIN, false) !== true) { \fwrite(STDERR, "Unable to set STDIN to non-blocking" . PHP_EOL); exit(1);}print "Write something and hit enter" . PHP_EOL;$suspension = EventLoop::getSuspension();$readableId = EventLoop::onReadable(STDIN, function ($id, $stream) use ($suspension): void { EventLoop::cancel($id); $chunk = \fread($stream, 8192); print "Read " . \strlen($chunk) . " bytes" . PHP_EOL; $suspension->resume(null);});$timeoutId = EventLoop::delay(5, function () use ($readableId, $suspension) { EventLoop::cancel($readableId); print "Timeout reached" . PHP_EOL; $suspension->resume(null);});$suspension->suspend();EventLoop::cancel($readableId);EventLoop::cancel($timeoutId);
显然,我们可以在这个例子中大略地同步利用 fgets(STDIN) 。我们只是在演示可以根据须要进失事宜循环,以稠浊同步任务和非壅塞任务。
Timers 定时器事宜循环公开了几种调度计时器的方法。
❝
Deferred 回调
defer() 调度回调在事宜循环的下一次迭代中实行。此方法担保了一个干净的调用堆栈,以避免循环确当前迭代中其他事宜的饥饿。defer()回调总是在事宜循环的下一个tick中实行。在 defer()计时器实行之后,它会被事宜循环自动垃圾网络,因此运用程序不须要手动取消关联的回调。像所有事宜回调一样,defer() 计时器可以被禁用和重新启用。如果您在调度它和它实际运行之间禁用此回调,则事宜循环将无法对其进行垃圾网络,直到它实行为止。因此,如果 defer() 回调从未真正实行以开释任何干系资源,则必须手动取消该回调。案例
<?php/ @author Tinywan(ShaoBo Wan) @email 756684177@qq.com @date 2024/1/31 18:24 /require 'vendor/autoload.php';use Revolt\EventLoop;echo "line 1\n";EventLoop::defer(function (): void { echo "line 3\n";});echo "line 2\n";EventLoop::run();
输出
line 1line 2line 3
❝
Delayed 回调
delay() 操持在延迟 n 秒后实行回调delay() 回调在实行后也会被事宜循环自动垃圾回收,运用程序不应该手动取消它,除非他们希望在实行前完备放弃回调。被禁用的 delay() 回调会重置其延迟韶光,以便重新启用后,原始延迟韶光再次从零开始。与 defer() 回调一样,如果定时器在创建后被运用程序禁用而无法运行,则必须手动取消操持用于一次性实行的定时器以开释资源。案例
<?php/ @author Tinywan(ShaoBo Wan) @email 756684177@qq.com @date 2024/1/31 18:24 /require 'vendor/autoload.php';use Revolt\EventLoop;EventLoop::delay(3, function (): void { print '3 seconds passed';});EventLoop::run();
3秒后输出
3 seconds passed
❝
Periodic 定期回调
repeat() 调度回调以每 n 秒重复实行一次。与所有其他事宜回调一样, repeat() 定时器可以随时禁用/重新启用。与 defer() 和 delay() 回调不同, repeat() 回调必须显式取消以开释关联的资源。一旦 repeat() 回调的目的实现,如果不能通过 cancel() 开释它们,将导致运用程序中的内存泄露。仅仅禁用 repeat() 回调是不足的,由于它们的数据只有在取消时才被开释。案例
<?php/ @author Tinywan(ShaoBo Wan) @email 756684177@qq.com @date 2024/1/31 18:49 /require 'vendor/autoload.php';use Revolt\EventLoop;EventLoop::repeat(0.1, function ($callbackId): void { static $i = 0; if ($i++ < 3) { echo "tick\n"; } else { EventLoop::cancel($callbackId); }});EventLoop::run();
输出
tickticktick
❝
定时器偏差
重复计时器基本上是大略的延迟计时器,在触发适当的处理程序之前会自动重新调度。它们受定时器漂移的影响。多个计时器可能会堆叠在一起,以防它们作为协程实行。
Fibers 纤程Revolt被设计为可以很好地与纤维一起事情。所有事宜回调都在单独的纤程中运行,并且可以随时挂起它。如果在事宜回调中没有挂起,则纤程将被重用于将来的事宜回调以保存资源。
挂起许可通过挂起当前实行高下文来等待事宜,直到所谈论的事宜发生。它们将挂起当前纤程并返回到事宜循环,或者如果从纤程外部(即从 {main} )调用,则开始运行事宜循环。
应利用 Revolt\EventLoop\Suspension API停息和规复光纤。Suspension 工具可以利用 Revolt\EventLoop::getSuspension() 创建。在得到 Suspension 工具之后,可以注册事宜回调以调度当前纤程的规复。$suspension->suspend() 将挂起当前的实行高下文,直到它通过 $suspension->resume() 或 $suspension->throw()规复。
❝
案例:让我们停息主实行高下文,直到有数据从 STDIN 读取或超时到期:
<?phprequire __DIR__ . '/vendor/autoload.php';use Revolt\EventLoop;if (\stream_set_blocking(STDIN, false) !== true) { \fwrite(STDERR, "Unable to set STDIN to non-blocking" . PHP_EOL); exit(1);}print "Write something and hit enter" . PHP_EOL;$suspension = EventLoop::getSuspension();$readableId = EventLoop::onReadable(STDIN, function ($id, $stream) use ($suspension): void { EventLoop::cancel($id); $chunk = \fread($stream, 8192); print "Read " . \strlen($chunk) . " bytes" . PHP_EOL; $suspension->resume(null);});$timeoutId = EventLoop::delay(5, function () use ($readableId, $suspension) { EventLoop::cancel($readableId); print "Timeout reached" . PHP_EOL; $suspension->resume(null);});$suspension->suspend();EventLoop::cancel($readableId);EventLoop::cancel($timeoutId);
❝
自动超时输出
Write something and hit enterTimeout reached
❝
按Enter键盘输出
Write something and hit enterRead 1 bytes
Signals 旗子暗记
旗子暗记是类Unix操作系统中的标准化。
EventLoop::onSignal() 可用于对发送到进程的旗子暗记作出反应。
<?phprequire __DIR__ . '/vendor/autoload.php';use Revolt\EventLoop;// Let's tick off output once per second, so we can see activity.EventLoop::repeat(1, function (): void { echo "tick: ", date('c'), "\n";});// What to do when a SIGINT signal is receivedEventLoop::onSignal(SIGINT, function (): void { echo "Caught SIGINT! exiting ...\n"; exit;});EventLoop::run();
❝
SIGINT 旗子暗记: 当用户按某些终端键时, 引发终端产生的旗子暗记. 如Ctrl+C键, 这将产生中断旗子暗记SIGINT. 它将停滞一个已失落去掌握的程序。
Ctrl+C 输出
tick: 2024-01-31T11:54:03+00:00tick: 2024-01-31T11:54:04+00:00tick: 2024-01-31T11:54:05+00:00tick: 2024-01-31T11:54:06+00:00tick: 2024-01-31T11:54:07+00:00tick: 2024-01-31T11:54:08+00:00tick: 2024-01-31T11:54:09+00:00tick: 2024-01-31T11:54:10+00:00tick: 2024-01-31T11:54:11+00:00tick: 2024-01-31T11:54:12+00:00^CCaught SIGINT! exiting ...
从基本事理中可以清楚地看到,旗子暗记回调可以像任何其他事宜回调一样被启用、禁用和取消。一样平常来说,如果所有回调都消逝了,只有旗子暗记回调仍旧存在,那么您希望退失事宜循环,除非您没有主动等待该事宜发生。
旗子暗记号可用性ext-uv 暴露 UV::SIG 常量用于可不雅观察旗子暗记。利用 EventDriver 的运用程序在注册旗子暗记回调或依赖 ext-pcntl 时须要手动指定适当的整数旗子暗记编号。