AMPHP es una colección de bibliotecas basadas en eventos para PHP diseñadas teniendo en cuenta las fibras y la concurrencia. amphp/amp
proporciona específicamente futuros y cancelaciones como primitivas fundamentales para la programación asincrónica. Ahora estamos usando Revolt en lugar de enviar una implementación de bucle de eventos con amphp/amp
.
Amp hace un uso intensivo de las fibras incluidas con PHP 8.1 para escribir código asíncrono como código de bloqueo síncrono. A diferencia de versiones anteriores, no hay necesidad de corrutinas ni devoluciones de llamadas basadas en generadores. De manera similar a los subprocesos, cada fibra tiene su propia pila de llamadas, pero el bucle de eventos programa las fibras de forma cooperativa. Utilice Ampasync()
para ejecutar cosas al mismo tiempo.
Tradicionalmente, PHP sigue un modelo de ejecución secuencial. El motor PHP ejecuta una línea tras otra en orden secuencial. Sin embargo, a menudo los programas constan de varios subprogramas independientes que pueden ejecutarse simultáneamente.
Si consulta una base de datos, envía la consulta y espera la respuesta del servidor de la base de datos de forma bloqueante. Una vez que tenga la respuesta, puede comenzar a hacer lo siguiente. En lugar de quedarnos sentados y no hacer nada mientras esperamos, ya podríamos enviar la siguiente consulta a la base de datos o realizar una llamada HTTP a una API. ¡Aprovechemos el tiempo que normalmente dedicamos a esperar E/S!
Revolt permite este tipo de operaciones de E/S simultáneas. Mantenemos la carga cognitiva baja evitando las devoluciones de llamadas. Nuestras API se pueden usar como cualquier otra biblioteca, excepto que las cosas también funcionan al mismo tiempo, porque usamos E/S sin bloqueo internamente. Ejecute cosas simultáneamente usando Ampasync()
y espere el resultado usando Future::await()
donde y cuando lo necesite.
Ha habido varias técnicas para implementar la concurrencia en PHP a lo largo de los años, por ejemplo, devoluciones de llamadas y generadores incluidos en PHP 5. Estos enfoques adolecían del problema "De qué color es tu función", que resolvimos al enviar Fibers con PHP 8.1. Permiten la concurrencia con múltiples pilas de llamadas independientes.
Las fibras se programan de forma cooperativa mediante el bucle de eventos, por lo que también se denominan corrutinas. Es importante comprender que solo se ejecuta una corrutina en un momento dado; mientras tanto, todas las demás corrutinas se suspenden.
Puede comparar rutinas con una computadora que ejecuta múltiples programas usando un solo núcleo de CPU. Cada programa tiene un intervalo de tiempo para ejecutarse. Sin embargo, las corrutinas no son preventivas. No obtienen su horario fijo. Tienen que ceder voluntariamente el control del ciclo de eventos.
Cualquier función de bloqueo de E/S bloquea todo el proceso mientras espera E/S. Querrás evitarlos. Si no ha leído la guía de instalación, eche un vistazo al ejemplo de Hello World que demuestra el efecto de bloquear funciones. Las bibliotecas proporcionadas por AMPHP evitan el bloqueo de E/S.
Este paquete se puede instalar como una dependencia de Composer.
composer require amphp/amp
Si usa esta biblioteca, es muy probable que desee programar eventos usando Revolt, que debería requerir por separado, incluso si se instala automáticamente como una dependencia.
composer require revolt/event-loop
Estos paquetes proporcionan los componentes básicos para aplicaciones asíncronas/concurrentes en PHP. Ofrecemos muchos paquetes además de estos, por ejemplo
amphp/byte-stream
proporciona una abstracción de flujoamphp/socket
proporciona una capa de socket para UDP y TCP, incluido TLSamphp/parallel
proporciona procesamiento paralelo para utilizar múltiples núcleos de CPU y descargar operaciones de bloqueoamphp/http-client
proporciona un cliente HTTP/1.1 y HTTP/2amphp/http-server
proporciona un servidor de aplicaciones HTTP/1.1 y HTTP/2amphp/mysql
y amphp/postgres
para acceso a bases de datos sin bloqueoEste paquete requiere PHP 8.1 o posterior. ¡No se requieren extensiones!
Las extensiones solo son necesarias si su aplicación requiere una gran cantidad de conexiones de socket simultáneas; generalmente este límite se configura en hasta 1024 descriptores de archivos.
Las corrutinas son funciones interrumpibles. En PHP, se pueden implementar utilizando fibras.
Nota Las versiones anteriores de Amp usaban generadores para un propósito similar, pero las fibras se pueden interrumpir en cualquier parte de la pila de llamadas, lo que hace innecesario el texto estándar anterior como
Ampcall()
.
En un momento dado, sólo está funcionando una fibra. Cuando una corrutina se suspende, su ejecución se interrumpe temporalmente, lo que permite ejecutar otras tareas. La ejecución se reanuda una vez que expira un temporizador, las operaciones de transmisión son posibles o se completa cualquier Future
esperado.
La suspensión de bajo nivel y la reanudación de las corrutinas están a cargo de la API Suspension
de 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 ;
Las devoluciones de llamada registradas en el bucle de eventos de Revolt se ejecutan automáticamente como corrutinas y es seguro suspenderlas. Además de la API de bucle de eventos, Ampasync()
se puede utilizar para iniciar una pila de llamadas independiente.
<?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 ;
Un Future
es un objeto que representa el resultado final de una operación asincrónica. Hay tres estados:
Un futuro completado con éxito es análogo a un valor de retorno, mientras que un futuro con error es análogo a generar una excepción.
Una forma de abordar las API asincrónicas es utilizar devoluciones de llamada que se pasan cuando se inicia la operación y se llaman una vez que se completa:
doSomething ( function ( $ error , $ value ) {
if ( $ error ) {
/* ... */
} else {
/* ... */
}
});
El método de devolución de llamada tiene varios inconvenientes.
Ahí es donde entran en juego los futuros. Son marcadores de posición para el resultado que se devuelven como cualquier otro valor de retorno. La persona que llama tiene la opción de esperar el resultado usando Future::await()
o registrar una o varias devoluciones de llamada.
try {
$ value = doSomething ()-> await ();
} catch (...) {
/* ... */
}
En las aplicaciones concurrentes, habrá múltiples futuros, donde es posible que quieras esperarlos a todos o solo al primero.
AmpFutureawait($iterable, $cancellation)
espera todos los objetos Future
de un iterable
. Si una de las instancias Future
falla, la operación se cancelará con esa excepción. De lo contrario, el resultado es una matriz que coincide con las claves de la entrada iterable
con sus valores de finalización.
El combinador await()
es extremadamente poderoso porque le permite ejecutar simultáneamente muchas operaciones asincrónicas al mismo tiempo. Veamos un ejemplo usando amphp/http-client
para recuperar múltiples recursos HTTP simultáneamente:
<?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)
es lo mismo que await()
excepto que tolera errores individuales. Se devuelve un resultado una vez que exactamente $count
instancias en el iterable
se completan correctamente. El valor de retorno es una matriz de valores. Las claves individuales en la matriz de componentes se conservan del iterable
pasado a la función para su evaluación.
AmpFutureawaitAll($iterable, $cancellation)
espera todos los futuros y devuelve sus resultados como una matriz [$errors, $values]
.
AmpFutureawaitFirst($iterable, $cancellation)
desenvuelve el primer Future
completado, ya sea que se haya completado correctamente o haya tenido un error.
AmpFutureawaitAny($iterable, $cancellation)
desenvuelve el primer Future
completado con éxito.
Los futuros se pueden crear de varias maneras. La mayoría del código usará Ampasync()
que toma una función y la ejecuta como rutina en otra Fiber.
A veces, una interfaz exige que se devuelva un Future
, pero los resultados están disponibles inmediatamente, por ejemplo, porque están almacenados en caché. En estos casos Future::complete(mixed)
y Future::error(Throwable)
se pueden utilizar para construir un Future
completado inmediatamente.
Nota La API
DeferredFuture
que se describe a continuación es una API avanzada que muchas aplicaciones probablemente no necesiten. UtiliceAmpasync()
o combinadores en su lugar cuando sea posible.
AmpDeferredFuture
es responsable de completar un Future
pendiente. Crea un AmpDeferredFuture
y utiliza su método getFuture
para devolver un AmpFuture
a la persona que llama. Una vez que el resultado esté listo, complete el Future
en poder de la persona que llama usando complete
o error
en el DeferredFuture
vinculado.
final class DeferredFuture
{
public function getFuture (): Future ;
public function complete ( mixed $ value = null );
public function error ( Throwable $ throwable );
}
Advertencia Si estás pasando objetos
DeferredFuture
, probablemente estés haciendo algo mal. Se supone que son el estado interno de su operación.
Advertencia No puedes completar un futuro con otro futuro; Utilice
Future::await()
antes de llamarDeferredFuture::complete()
en tales casos.
Aquí hay un ejemplo simple de un productor de valores asíncrono asyncMultiply()
que crea un DeferredFuture
y devuelve el Future
asociado a su llamador.
<?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)
Cada operación que admite la cancelación acepta una instancia de Cancellation
como argumento. Las cancelaciones son objetos que permiten a los controladores de registro suscribirse a solicitudes de cancelación. Estos objetos se transmiten a suboperaciones o deben ser manejados por la propia operación.
$cancellation->throwIfRequested()
se puede utilizar para fallar la operación actual con una CancelledException
una vez que se ha solicitado la cancelación. Si bien throwIfRequested()
funciona bien, es posible que algunas operaciones quieran suscribirse con una devolución de llamada. Pueden hacerlo usando Cancellation::subscribe()
para suscribir cualquier solicitud de cancelación que pueda ocurrir.
La persona que llama crea una Cancellation
utilizando una de las implementaciones siguientes.
Nota Las cancelaciones son solo de asesoramiento. Un solucionador de DNS puede ignorar las solicitudes de cancelación después de que se haya enviado la consulta, ya que la respuesta debe procesarse de todos modos y aún puede almacenarse en caché. Un cliente HTTP podría continuar con una solicitud HTTP casi finalizada para reutilizar la conexión, pero podría abortar una respuesta de codificación fragmentada ya que no puede saber si continuar es realmente más barato que abortar.
Un TimeoutCancellations
se cancela automáticamente después del número de segundos especificado.
request ( " ... " , new Amp TimeoutCancellation ( 30 ));
Una SignalCancellation
se cancela automáticamente después de que el proceso actual haya recibido una señal específica.
request ( " ... " , new Amp SignalCancellation ( SIGINT ));
Una DeferredCancellation
permite la cancelación manual con la llamada de un método. Esta es la forma preferida si necesita registrar alguna devolución de llamada personalizada en algún lugar en lugar de enviar su propia implementación. Solo la persona que llama tiene acceso a DeferredCancellation
y puede cancelar la operación usando DeferredCancellation::cancel()
.
$ deferredCancellation = new Amp DeferredCancellation ();
// Register some custom callback somewhere
onSomeEvent ( fn () => $ deferredCancellation -> cancel ());
request ( " ... " , $ deferredCancellation -> getCancellation ());
Una NullCancellation
nunca se cancelará. La cancelación suele ser opcional y normalmente se implementa haciendo que el parámetro sea anulable. Para evitar guardias como if ($cancellation)
, se puede usar NullCancellation
en su lugar.
$ cancellation ??= new NullCancellationToken ();
Una CompositeCancellation
combina múltiples objetos de cancelación independientes. Si se cancela alguna de estas cancelaciones, se cancelará la propia CompositeCancellation
.
amphp/amp
sigue la especificación de versiones semánticas semver como todos los demás paquetes amphp
.
Los paquetes compatibles deben utilizar el tema amphp
en GitHub.
Si descubre algún problema relacionado con la seguridad, envíeme un correo electrónico [email protected]
en lugar de utilizar el rastreador de problemas.
La Licencia MIT (MIT). Consulte LICENSE
para obtener más información.