AMPHP é uma coleção de bibliotecas orientadas a eventos para PHP projetadas com fibras e simultaneidade em mente. amphp/amp
fornece especificamente futuros e cancelamentos como primitivos fundamentais para programação assíncrona. Agora estamos usando o Revolt em vez de enviar uma implementação de loop de eventos com amphp/amp
.
Amp faz uso intenso de fibras fornecidas com PHP 8.1 para escrever código assíncrono, assim como código de bloqueio síncrono. Em contraste com as versões anteriores, não há necessidade de corrotinas ou retornos de chamada baseados em gerador. Semelhante aos threads, cada fibra possui sua própria pilha de chamadas, mas as fibras são escalonadas cooperativamente pelo loop de eventos. Use Ampasync()
para executar coisas simultaneamente.
Tradicionalmente, o PHP segue um modelo de execução sequencial. O mecanismo PHP executa uma linha após a outra em ordem sequencial. Freqüentemente, entretanto, os programas consistem em vários subprogramas independentes que podem ser executados simultaneamente.
Se você consultar um banco de dados, você envia a consulta e aguarda a resposta do servidor de banco de dados de forma bloqueadora. Depois de obter a resposta, você pode começar a fazer a próxima coisa. Em vez de ficarmos sentados sem fazer nada enquanto esperamos, já poderíamos enviar a próxima consulta ao banco de dados ou fazer uma chamada HTTP para uma API. Vamos aproveitar o tempo que normalmente gastamos esperando por I/O!
O Revolt permite tais operações de E/S simultâneas. Mantemos a carga cognitiva baixa evitando retornos de chamada. Nossas APIs podem ser usadas como qualquer outra biblioteca, exceto que as coisas também funcionam simultaneamente, porque usamos E/S sem bloqueio nos bastidores. Execute coisas simultaneamente usando Ampasync()
e aguarde o resultado usando Future::await()
onde e quando você precisar!
Houve várias técnicas para implementar simultaneidade em PHP ao longo dos anos, por exemplo, retornos de chamada e geradores fornecidos no PHP 5. Essas abordagens sofriam com o problema "Qual é a cor da sua função", que resolvemos enviando o Fibers com o PHP 8.1. Eles permitem a simultaneidade com várias pilhas de chamadas independentes.
As fibras são agendadas cooperativamente pelo loop de eventos, por isso também são chamadas de corrotinas. É importante entender que apenas uma corrotina está em execução em um determinado momento; todas as outras corrotinas são suspensas nesse meio tempo.
Você pode comparar corrotinas a um computador executando vários programas usando um único núcleo de CPU. Cada programa recebe um intervalo de tempo para ser executado. As corrotinas, no entanto, não são preemptivas. Eles não recebem seu horário fixo. Eles têm que ceder voluntariamente o controle do loop de eventos.
Qualquer função de bloqueio de E/S bloqueia todo o processo enquanto espera pela E/S. Você vai querer evitá-los. Se você não leu o guia de instalação, dê uma olhada no exemplo Hello World que demonstra o efeito das funções de bloqueio. As bibliotecas fornecidas pelo AMPHP evitam o bloqueio de E/S.
Este pacote pode ser instalado como uma dependência do Composer.
composer require amphp/amp
Se você usa esta biblioteca, é muito provável que queira agendar eventos usando o Revolt, que você deve solicitar separadamente, mesmo que seja instalado automaticamente como uma dependência.
composer require revolt/event-loop
Esses pacotes fornecem os blocos de construção básicos para aplicativos assíncronos/simultâneos em PHP. Oferecemos muitos pacotes baseados neles, por exemplo
amphp/byte-stream
fornecendo uma abstração de fluxoamphp/socket
fornecendo uma camada de soquete para UDP e TCP incluindo TLSamphp/parallel
fornecendo processamento paralelo para utilizar vários núcleos de CPU e descarregar operações de bloqueioamphp/http-client
fornecendo um cliente HTTP/1.1 e HTTP/2amphp/http-server
fornecendo um servidor de aplicativos HTTP/1.1 e HTTP/2amphp/mysql
e amphp/postgres
para acesso sem bloqueio ao banco de dadosEste pacote requer PHP 8.1 ou posterior. Não são necessárias extensões!
As extensões só serão necessárias se seu aplicativo precisar de um grande número de conexões de soquete simultâneas. Geralmente, esse limite é configurado para até 1.024 descritores de arquivo.
Corrotinas são funções interrompíveis. Em PHP, eles podem ser implementados usando fibras.
Nota As versões anteriores do Amp usavam geradores para uma finalidade semelhante, mas as fibras podem ser interrompidas em qualquer lugar da pilha de chamadas, tornando desnecessário o padrão anterior como
Ampcall()
.
A qualquer momento, apenas uma fibra está funcionando. Quando uma corrotina é suspensa, a execução da corrotina é temporariamente interrompida, permitindo que outras tarefas sejam executadas. A execução é retomada quando um cronômetro expira, operações de fluxo são possíveis ou qualquer Future
aguardado é concluído.
A suspensão de baixo nível e a retomada de corrotinas são tratadas pela API Suspension
do 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 ;
Os retornos de chamada registrados no loop de eventos do Revolt são executados automaticamente como corrotinas e é seguro suspendê-los. Além da API de loop de eventos, Ampasync()
pode ser usado para iniciar uma pilha de chamadas independente.
<?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 ;
Um Future
é um objeto que representa o resultado eventual de uma operação assíncrona. Existem três estados:
Um futuro concluído com sucesso é análogo a um valor de retorno, enquanto um futuro com erro é análogo a lançar uma exceção.
Uma maneira de abordar APIs assíncronas é usar retornos de chamada que são passados quando a operação é iniciada e chamados quando ela é concluída:
doSomething ( function ( $ error , $ value ) {
if ( $ error ) {
/* ... */
} else {
/* ... */
}
});
A abordagem de retorno de chamada tem várias desvantagens.
É aí que os futuros entram em jogo. Eles são espaços reservados para o resultado retornado como qualquer outro valor de retorno. O chamador tem a opção de aguardar o resultado usando Future::await()
ou registrar um ou vários retornos de chamada.
try {
$ value = doSomething ()-> await ();
} catch (...) {
/* ... */
}
Em aplicações simultâneas, haverá vários futuros, onde você pode querer aguardar todos eles ou apenas o primeiro.
AmpFutureawait($iterable, $cancellation)
aguarda todos os objetos Future
de um iterable
. Se uma das instâncias Future
falhar, a operação será abortada com essa exceção. Caso contrário, o resultado é uma matriz que corresponde às chaves do iterable
de entrada aos seus valores de conclusão.
O combinador await()
é extremamente poderoso porque permite executar simultaneamente muitas operações assíncronas ao mesmo tempo. Vejamos um exemplo usando amphp/http-client
para recuperar vários recursos HTTP simultaneamente:
<?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)
é o mesmo que await()
exceto que tolera erros individuais. Um resultado é retornado quando exatamente $count
instâncias no iterable
são concluídas com sucesso. O valor de retorno é uma matriz de valores. As chaves individuais na matriz de componentes são preservadas do iterable
passado para a função para avaliação.
AmpFutureawaitAll($iterable, $cancellation)
aguarda todos os futuros e retorna seus resultados como array [$errors, $values]
.
AmpFutureawaitFirst($iterable, $cancellation)
desembrulha o primeiro Future
concluído, seja concluído com êxito ou com erro.
AmpFutureawaitAny($iterable, $cancellation)
desembrulha o primeiro Future
concluído com sucesso.
Os futuros podem ser criados de diversas maneiras. A maior parte do código usará Ampasync()
que pega uma função e a executa como corrotina em outra fibra.
Às vezes, uma interface exige que um Future
seja retornado, mas os resultados ficam imediatamente disponíveis, por exemplo, porque estão armazenados em cache. Nestes casos Future::complete(mixed)
e Future::error(Throwable)
podem ser usados para construir um Future
imediatamente concluído.
Nota A API
DeferredFuture
descrita abaixo é uma API avançada que muitos aplicativos provavelmente não precisam. UseAmpasync()
ou combinadores sempre que possível.
AmpDeferredFuture
é responsável por completar um Future
pendente. Você cria um AmpDeferredFuture
e usa seu método getFuture
para retornar um AmpFuture
ao chamador. Assim que o resultado estiver pronto, você completa o Future
mantido pelo chamador usando complete
ou error
no DeferredFuture
vinculado.
final class DeferredFuture
{
public function getFuture (): Future ;
public function complete ( mixed $ value = null );
public function error ( Throwable $ throwable );
}
Aviso Se você estiver passando objetos
DeferredFuture
, provavelmente está fazendo algo errado. Eles deveriam ser o estado interno da sua operação.
Aviso Você não pode completar um futuro com outro futuro; Use
Future::await()
antes de chamarDeferredFuture::complete()
nesses casos.
Aqui está um exemplo simples de um produtor de valor assíncrono asyncMultiply()
criando um DeferredFuture
e retornando o Future
associado ao seu chamador.
<?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)
Toda operação que suporta cancelamento aceita uma instância de Cancellation
como argumento. Cancelamentos são objetos que permitem registrar manipuladores para assinar solicitações de cancelamento. Esses objetos são passados para suboperações ou precisam ser manipulados pela própria operação.
$cancellation->throwIfRequested()
pode ser usado para falhar na operação atual com uma CancelledException
assim que o cancelamento for solicitado. Embora throwIfRequested()
funcione bem, algumas operações podem querer se inscrever com um retorno de chamada. Eles podem fazer isso usando Cancellation::subscribe()
para assinar quaisquer solicitações de cancelamento que possam acontecer.
O chamador cria um Cancellation
usando uma das implementações abaixo.
Nota Os cancelamentos são apenas consultivos. Um resolvedor de DNS pode ignorar solicitações de cancelamento após o envio da consulta, pois a resposta precisa ser processada de qualquer maneira e ainda pode ser armazenada em cache. Um cliente HTTP pode continuar uma solicitação HTTP quase concluída para reutilizar a conexão, mas pode abortar uma resposta de codificação fragmentada, pois não pode saber se continuar é realmente mais barato do que abortar.
Um TimeoutCancellations
se cancela automaticamente após o número especificado de segundos.
request ( " ... " , new Amp TimeoutCancellation ( 30 ));
Um SignalCancellation
se cancela automaticamente após um sinal especificado ter sido recebido pelo processo atual.
request ( " ... " , new Amp SignalCancellation ( SIGINT ));
Um DeferredCancellation
permite o cancelamento manual com a chamada de um método. Esta é a maneira preferida se você precisar registrar algum retorno de chamada personalizado em algum lugar, em vez de enviar sua própria implementação. Somente o chamador tem acesso ao DeferredCancellation
e pode cancelar a operação usando DeferredCancellation::cancel()
.
$ deferredCancellation = new Amp DeferredCancellation ();
// Register some custom callback somewhere
onSomeEvent ( fn () => $ deferredCancellation -> cancel ());
request ( " ... " , $ deferredCancellation -> getCancellation ());
Um NullCancellation
nunca será cancelado. O cancelamento geralmente é opcional, o que geralmente é implementado tornando o parâmetro anulável. Para evitar guardas como if ($cancellation)
, um NullCancellation
pode ser usado.
$ cancellation ??= new NullCancellationToken ();
Um CompositeCancellation
combina vários objetos de cancelamento independentes. Se algum desses cancelamentos for cancelado, o próprio CompositeCancellation
será cancelado.
amphp/amp
segue a especificação de versionamento semântico semver como todos os outros pacotes amphp
.
Pacotes compatíveis devem usar o tópico amphp
no GitHub.
Se você descobrir algum problema relacionado à segurança, envie um e-mail para [email protected]
em vez de usar o rastreador de problemas.
A licença MIT (MIT). Consulte LICENSE
para obter mais informações.