Revolt是并发PHP应用程序的坚如磐石的事件循环。通常的PHP应用程序将大部分时间花在等待I/O上。虽然PHP是单线程的,但可以使用协作多任务来允许并发性,方法是使用等待时间来做不同的事情。
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) 。我们只是在演示可以根据需要进出事件循环,以混合同步任务和非阻塞任务。
事件循环公开了几种调度计时器的方法。
Deferred 回调
案例
<?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 回调
案例
<?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 定期回调
案例
<?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
定时器偏差
重复计时器基本上是简单的延迟计时器,在触发适当的处理程序之前会自动重新调度。它们受定时器漂移的影响。多个计时器可能会堆叠在一起,以防它们作为协程执行。
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
信号是类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 时需要手动指定适当的整数信号编号。
本文链接:http://www.28at.com/showinfo-26-70393-0.htmlPHP 高性能的事件循环库 Revolt
声明:本网页内容旨在传播知识,不代表本站观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。