AMPHP — это коллекция управляемых событиями библиотек для PHP, разработанных с учетом волокон и параллелизма. amphp/amp
специально предоставляет фьючерсы и отмены в качестве фундаментальных примитивов асинхронного программирования. Теперь мы используем Revolt вместо реализации цикла событий с помощью amphp/amp
.
Amp активно использует волокна, поставляемые с PHP 8.1, для написания асинхронного кода, точно так же, как синхронный, блокирующий код. В отличие от более ранних версий, здесь нет необходимости в сопрограммах или обратных вызовах на основе генератора. Подобно потокам, каждое волокно имеет свой собственный стек вызовов, но волокна планируются совместно с помощью цикла событий. Используйте Ampasync()
для одновременного запуска.
Традиционно PHP придерживается модели последовательного выполнения. Механизм PHP выполняет одну строку за другой в последовательном порядке. Однако часто программы состоят из нескольких независимых подпрограмм, которые могут выполняться одновременно.
Если вы запрашиваете базу данных, вы отправляете запрос и ожидаете ответа от сервера базы данных в блокирующем режиме. Получив ответ, вы можете начать делать следующее. Вместо того, чтобы сидеть и ничего не делать в ожидании, мы могли бы уже отправить следующий запрос к базе данных или выполнить HTTP-вызов API. Давайте воспользуемся временем, которое мы обычно тратим на ожидание ввода-вывода!
Revolt допускает такие одновременные операции ввода-вывода. Мы поддерживаем низкую когнитивную нагрузку, избегая обратных вызовов. Наши API можно использовать, как и любую другую библиотеку, за исключением того, что все они работают одновременно, поскольку внутри мы используем неблокирующий ввод-вывод. Запускайте все одновременно, используя Ampasync()
и ждите результата, используя Future::await()
где и когда вам это нужно!
На протяжении многих лет существовали различные методы реализации параллелизма в PHP, например, обратные вызовы и генераторы, представленные в PHP 5. Эти подходы пострадали от проблемы «Какого цвета ваша функция», которую мы решили, выпустив Fibers с PHP 8.1. Они допускают параллелизм с несколькими независимыми стеками вызовов.
Файберы совместно планируются циклом событий, поэтому их также называют сопрограммами. Важно понимать, что в любой момент времени работает только одна сопрограмма, все остальные сопрограммы в это время приостанавливаются.
Вы можете сравнить сопрограммы с компьютером, на котором выполняется несколько программ, использующих одно ядро ЦП. Каждая программа получает временной интервал для выполнения. Однако сопрограммы не являются вытесняющими. У них нет фиксированного временного интервала. Им приходится добровольно передать управление циклу событий.
Любая функция блокировки ввода-вывода блокирует весь процесс во время ожидания ввода-вывода. Вы захотите избежать их. Если вы не читали руководство по установке, взгляните на пример Hello World, демонстрирующий эффект блокировки функций. Библиотеки, предоставляемые AMPHP, избегают блокировки ввода-вывода.
Этот пакет можно установить как зависимость Composer.
composer require amphp/amp
Если вы используете эту библиотеку, весьма вероятно, что вы захотите планировать события с помощью Revolt, который вам следует потребовать отдельно, даже если он автоматически устанавливается как зависимость.
composer require revolt/event-loop
Эти пакеты предоставляют основные строительные блоки для асинхронных/параллельных приложений на PHP. Мы предлагаем множество пакетов, созданных на основе этих, например
amphp/byte-stream
предоставляющий абстракцию потокаamphp/socket
обеспечивающий уровень сокетов для UDP и TCP, включая TLSamphp/parallel
обеспечивает параллельную обработку для использования нескольких ядер ЦП и разгрузки операций блокировки.amphp/http-client
, предоставляющий клиент HTTP/1.1 и HTTP/2amphp/http-server
предоставляющий сервер приложений HTTP/1.1 и HTTP/2amphp/mysql
и amphp/postgres
для неблокирующего доступа к базе данныхДля этого пакета требуется PHP 8.1 или более поздняя версия. Никаких расширений не требуется!
Расширения необходимы только в том случае, если вашему приложению требуется большое количество одновременных подключений к сокетам. Обычно этот предел настраивается до 1024 файловых дескрипторов.
Сопрограммы — это прерываемые функции. В PHP их можно реализовать с помощью волокон.
Примечание. Предыдущие версии Amp использовали генераторы для аналогичной цели, но волокна могут быть прерваны в любом месте стека вызовов, что делает ненужным предыдущий шаблон, такой как
Ampcall()
.
В любой момент времени работает только одно волокно. Когда сопрограмма приостанавливается, ее выполнение временно прерывается, позволяя запускать другие задачи. Выполнение возобновляется по истечении времени таймера, когда потоковые операции становятся возможными или любое ожидаемое Future
завершается.
Низкоуровневая приостановка и возобновление сопрограмм осуществляется с помощью API Suspension
API Revolt.
<?php
require __DIR__ . ' /vendor/autoload.php ' ;
use Revolt EventLoop ;
$ suspension = EventLoop:: getSuspension ();
EventLoop:: delay ( 5 , function () use ( $ suspension ): void {
print ' ++ Executing callback created by EventLoop::delay() ' . PHP_EOL ;
$ suspension -> resume ( null );
});
print ' ++ Suspending to event loop... ' . PHP_EOL ;
$ suspension -> suspend ();
print ' ++ Script end ' . PHP_EOL ;
Обратные вызовы, зарегистрированные в цикле событий Revolt, автоматически запускаются как сопрограммы, и их можно безопасно приостановить. Помимо API цикла событий, Ampasync()
можно использовать для запуска независимого стека вызовов.
<?php
use function Amp delay ;
require __DIR__ . ' /vendor/autoload.php ' ;
Amp async ( function () {
print ' ++ Executing callback passed to async() ' . PHP_EOL ;
delay ( 3 );
print ' ++ Finished callback passed to async() ' . PHP_EOL ;
});
print ' ++ Suspending to event loop... ' . PHP_EOL ;
delay ( 5 );
print ' ++ Script end ' . PHP_EOL ;
Future
— это объект, представляющий конечный результат асинхронной операции. Есть три состояния:
Успешно завершенное будущее аналогично возвращаемому значению, а ошибочное будущее — аналогу выдачи исключения.
Один из способов использования асинхронных API — использование обратных вызовов, которые передаются при запуске операции и вызываются после ее завершения:
doSomething ( function ( $ error , $ value ) {
if ( $ error ) {
/* ... */
} else {
/* ... */
}
});
Метод обратного вызова имеет несколько недостатков.
Именно здесь в игру вступают фьючерсы. Это заполнители для результата, которые возвращаются, как и любое другое возвращаемое значение. Вызывающий имеет выбор: дождаться результата с помощью Future::await()
или зарегистрировать один или несколько обратных вызовов.
try {
$ value = doSomething ()-> await ();
} catch (...) {
/* ... */
}
В параллельных приложениях будет несколько вариантов будущего, и вы можете ожидать их всех или только первого.
AmpFutureawait($iterable, $cancellation)
ожидает все объекты Future
iterable
объекта. Если в одном из экземпляров Future
произойдет ошибка, операция будет прервана с этим исключением. В противном случае результатом является массив, соответствующий ключам от iterable
входных данных до их значений завершения.
Комбинатор await()
чрезвычайно мощный, поскольку позволяет одновременно выполнять множество асинхронных операций. Давайте рассмотрим пример использования amphp/http-client
для одновременного получения нескольких HTTP-ресурсов:
<?php
use Amp Future ;
use Amp Http Client HttpClientBuilder ;
use Amp Http Client Request ;
$ httpClient = HttpClientBuilder:: buildDefault ();
$ uris = [
" google " => " https://www.google.com " ,
" news " => " https://news.google.com " ,
" bing " => " https://www.bing.com " ,
" yahoo " => " https://www.yahoo.com " ,
];
try {
$ responses = Future await ( array_map ( function ( $ uri ) use ( $ httpClient ) {
return Amp async ( fn () => $ httpClient -> request ( new Request ( $ uri , ' HEAD ' )));
}, $ uris ));
foreach ( $ responses as $ key => $ response ) {
printf (
" %s | HTTP/%s %d %s n" ,
$ key ,
$ response -> getProtocolVersion (),
$ response -> getStatus (),
$ response -> getReason ()
);
}
} catch ( Exception $ e ) {
// If any one of the requests fails the combo will fail
echo $ e -> getMessage (), "n" ;
}
AmpFutureawaitAnyN($count, $iterable, $cancellation)
аналогичен await()
за исключением того, что он допускает отдельные ошибки. Результат возвращается после успешного завершения ровно $count
экземпляров в iterable
. Возвращаемое значение представляет собой массив значений. Отдельные ключи в массиве компонентов сохраняются из iterable
передаваемой функции для оценки.
AmpFutureawaitAll($iterable, $cancellation)
ожидает все фьючерсы и возвращает их результаты в виде массива [$errors, $values]
.
AmpFutureawaitFirst($iterable, $cancellation)
разворачивает первый завершенный Future
, независимо от того, завершился ли он успешно или с ошибкой.
AmpFutureawaitAny($iterable, $cancellation)
разворачивает первый успешно завершенный Future
.
Фьючерсы могут создаваться несколькими способами. Большая часть кода будет использовать Ampasync()
, который принимает функцию и запускает ее как сопрограмму в другом Fiber.
Иногда интерфейс требует возврата Future
, но результаты доступны немедленно, например, потому что они кэшируются. В этих случаях Future::complete(mixed)
и Future::error(Throwable)
можно использовать для создания немедленно завершенного Future
.
Примечание. Описанный ниже API
DeferredFuture
— это расширенный API, который, вероятно, не нужен многим приложениям. Вместо этого используйтеAmpasync()
или комбинаторы, где это возможно.
AmpDeferredFuture
отвечает за завершение ожидающего Future
. Вы создаете AmpDeferredFuture
и используете его метод getFuture
для возврата AmpFuture
вызывающей стороне. Как только результат готов, вы завершаете Future
удерживаемый вызывающей стороной, используя complete
или error
в связанном DeferredFuture
.
final class DeferredFuture
{
public function getFuture (): Future ;
public function complete ( mixed $ value = null );
public function error ( Throwable $ throwable );
}
Предупреждение. Если вы передаете объекты
DeferredFuture
, вы, вероятно, делаете что-то неправильно. Они должны быть внутренним состоянием вашей операции.
Предупреждение. Вы не можете завершить будущее другим будущим; В таких случаях используйте
Future::await()
перед вызовомDeferredFuture::complete()
.
Вот простой пример асинхронного производителя значений asyncMultiply()
создающего DeferredFuture
и возвращающего связанное Future
вызывающему объекту.
<?php // Example async producer using DeferredFuture
use Revolt EventLoop ;
function asyncMultiply ( int $ x , int $ y ): Future
{
$ deferred = new Amp DeferredFuture ;
// Complete the async result one second from now
EventLoop:: delay ( 1 , function () use ( $ deferred , $ x , $ y ) {
$ deferred -> complete ( $ x * $ y );
});
return $ deferred -> getFuture ();
}
$ future = asyncMultiply ( 6 , 7 );
$ result = $ future -> await ();
var_dump ( $ result ); // int(42)
Каждая операция, поддерживающая отмену, принимает экземпляр Cancellation
в качестве аргумента. Отмены — это объекты, которые позволяют регистрировать обработчики для подписки на запросы на отмену. Эти объекты передаются в подоперации или должны обрабатываться самой операцией.
$cancellation->throwIfRequested()
можно использовать для сбоя текущей операции с CancelledException
после запроса отмены. Хотя throwIfRequested()
работает хорошо, некоторые операции вместо этого могут захотеть подписаться с помощью обратного вызова. Они могут сделать это, используя Cancellation::subscribe()
, чтобы подписаться на любые запросы на отмену, которые могут произойти.
Вызывающий объект создает Cancellation
, используя одну из приведенных ниже реализаций.
Примечание. Отмена носит исключительно рекомендательный характер. Распознаватель DNS может игнорировать запросы на отмену после отправки запроса, поскольку ответ в любом случае должен быть обработан и его все равно можно кэшировать. HTTP-клиент может продолжить почти завершенный HTTP-запрос для повторного использования соединения, но может прервать ответ с фрагментированным кодированием, поскольку он не может знать, действительно ли продолжение дешевле, чем прерывание.
TimeoutCancellations
автоматически отменяется через указанное количество секунд.
request ( " ... " , new Amp TimeoutCancellation ( 30 ));
SignalCancellation
автоматически отменяет себя после получения заданного сигнала текущим процессом.
request ( " ... " , new Amp SignalCancellation ( SIGINT ));
DeferredCancellation
позволяет отменить операцию вручную с помощью вызова метода. Это предпочтительный способ, если вам нужно где-то зарегистрировать какой-то собственный обратный вызов вместо того, чтобы отправлять собственную реализацию. Только вызывающая сторона имеет доступ к DeferredCancellation
и может отменить операцию с помощью DeferredCancellation::cancel()
.
$ deferredCancellation = new Amp DeferredCancellation ();
// Register some custom callback somewhere
onSomeEvent ( fn () => $ deferredCancellation -> cancel ());
request ( " ... " , $ deferredCancellation -> getCancellation ());
NullCancellation
никогда не будет отменен. Отмена часто не является обязательной, что обычно реализуется путем присвоения параметру значения null. Чтобы избежать таких ограничений, как if ($cancellation)
, вместо этого можно использовать NullCancellation
.
$ cancellation ??= new NullCancellationToken ();
CompositeCancellation
объединяет несколько независимых объектов отмены. Если какая-либо из этих отмен будет отменена, сама CompositeCancellation
будет отменена.
amphp/amp
соответствует спецификации семантического управления версиями semver, как и все другие пакеты amphp
.
Совместимые пакеты должны использовать тему amphp
на GitHub.
Если вы обнаружите какие-либо проблемы, связанные с безопасностью, отправьте электронное письмо по адресу [email protected]
вместо использования системы отслеживания проблем.
Лицензия MIT (MIT). Пожалуйста, смотрите LICENSE
для получения дополнительной информации.