PHP assíncrono

Há dez anos, tínhamos uma pilha LAMP clássica: Linux, Apache, MySQL e PHP, que funcionavam no modo lento do mod_php. O mundo mudou, e com ele a importância da velocidade. Apareceu o PHP-FPM, que permitiu aumentar significativamente o desempenho das soluções em PHP e não reescrever urgentemente para algo mais rápido.

Paralelamente, a biblioteca ReactPHP estava sendo desenvolvida usando o conceito Loop de Eventos para processar sinais do SO e apresentar resultados para operações assíncronas. O desenvolvimento da idéia do ReactPHP - AMPHP. Esta biblioteca usa o mesmo loop de eventos, mas suporta corotinas, ao contrário do ReactPHP. Eles permitem que você escreva código assíncrono que pareça síncrono. Talvez esta seja a estrutura mais atual para o desenvolvimento de aplicativos assíncronos em PHP.



Mas a velocidade é cada vez mais necessária, as ferramentas já não são suficientes, portanto a idéia de programação assíncrona no PHP é uma das maneiras de acelerar o processamento de consultas e utilizar melhor os recursos.

É disso que Anton Shabovta vai falar (zloyusr) É desenvolvedor da Onliner. Experiência há mais de 10 anos: comecei com aplicativos de desktop em C / C ++ e depois mudei para o desenvolvimento web em PHP. Ele escreve projetos "Home" em C # e Python 3 e, em PHP, está experimentando DDD, CQRS, Event Sourcing, Async Multitarefa.

Este artigo é baseado em uma transcrição do relatório de Anton no PHP Russia 2019 . Nele entenderemos as operações de bloqueio e não-bloqueio no PHP, estudaremos a estrutura do Loop de eventos e primitivas assíncronas, como Promise e coroutines, a partir de dentro. Por fim, descobriremos o que nos espera no ext-async, AMPHP 3 e PHP 8.


Introduzimos algumas definições. Durante muito tempo, tentei encontrar uma definição exata de assincronia e operações assíncronas, mas não encontrei e escrevi as minhas.
Assincronia é a capacidade de um sistema de software de não bloquear o thread principal de execução.
Uma operação assíncrona é uma operação que não bloqueia o fluxo de execução de um programa até sua conclusão.

Parece ser simples, mas primeiro você precisa entender quais operações bloqueiam o fluxo de execução.

Operações de bloqueio


PHP é uma linguagem de intérprete. Ele lê o código linha por linha, traduz em suas instruções e executa. Em qual linha do exemplo abaixo o código será bloqueado?

public function update(User $user)
{
    try {
        $sql = 'UPDATE users SET ...';
        return $this->connection->execute($sql, $user->data());
    } catch (\PDOException $error) {
        log($error->getMessage());
    }

    return 0;
}

Se conectar ao banco de dados via PDO, o segmento de execução será bloqueado na cadeia de consulta para SQL-servidor: return $this->connection->execute($sql, $user->data());.

Isso ocorre porque o PHP não sabe por quanto tempo o servidor SQL processará essa consulta e se será executada. Aguarda uma resposta do servidor e todo esse tempo o programa não está sendo executado.

O PHP também bloqueia o fluxo de execução em todas as operações de E / S.

  • Sistema de arquivos : fwrite, file_get_contents.
  • Bases de dados : PDOConnection, RedisClient. Quase todas as extensões para conectar um banco de dados funcionam no modo de bloqueio por padrão.
  • Processos : exec, system, proc_open. Essas são operações de bloqueio, pois todo o trabalho com processos é criado por meio de chamadas do sistema.
  • Trabalhando com stdin / stdout : readline, echo, print.

Além disso, a execução é bloqueada nos temporizadores : sleep, usleep. Essas são operações nas quais explicitamente dizemos ao thread para adormecer por um tempo. O PHP ficará ocioso esse tempo todo.

Cliente SQL assíncrono


Mas o PHP moderno é uma linguagem de uso geral, e não apenas para a web como PHP / FI em 1997. Portanto, podemos escrever um cliente SQL assíncrono do zero. A tarefa não é a mais trivial, mas solucionável.

public function execAsync(string $query, array $params = [])
{
    $socket = stream_socket_client('127.0.0.1:3306', ...);

    stream_set_blocking($socket, false);

    $data = $this->packBinarySQL($query, $params);
    
    socket_write($socket, $data, strlen($data));
}

O que esse cliente faz? Ele se conecta ao nosso servidor SQL, coloca o soquete no modo sem bloqueio, empacota a solicitação em um formato binário que o servidor SQL entende, grava dados no soquete.

Como o soquete está no modo sem bloqueio, a operação de gravação do PHP é rápida.

Mas o que retornará como resultado dessa operação? Não sabemos o que o servidor SQL responderá. Pode levar muito tempo para concluir a solicitação ou não. Mas algo precisa ser devolvido? Se usarmos o PDO e chamarmos a updateconsulta no servidor SQL, retornaremos affected rows- o número de linhas alteradas por essa consulta. Ainda não podemos devolvê-lo, portanto, apenas prometemos um retorno.

Promessa


Este é um conceito do mundo da programação assíncrona.
Promise é um objeto wrapper sobre o resultado de uma operação assíncrona. Além disso, o resultado da operação ainda é desconhecido para nós.
Infelizmente, não existe um padrão único da Promise e não é possível transferir padrões diretamente do mundo JavaScript para o PHP.

Como a promessa funciona


Como ainda não há resultado, só podemos estabelecer alguns callbacks.



Quando os dados estão disponíveis, é necessário executar um retorno de chamada onResolve.



Se ocorrer um erro, um retorno de chamada será executado onRejectpara lidar com o erro.



A interface do Promise se parece com isso.

interface Promise
{
    const
        STATUS_PENDING = 0,
        STATUS_RESOLVED = 1,
        STATUS_REJECTED = 2
    ;

    public function onResolve(callable $callback);
    public function onReject(callable $callback);
    public function resolve($data);
    public function reject(\Throwable $error);
}

Promise possui status e métodos para definir retornos de chamada e preencher ( resolve) Promise com dados ou erro ( reject). Mas existem diferenças e variações. Os métodos podem ser chamados de maneira diferente, ou em vez de métodos separados para estabelecer retornos de chamada, resolvee rejectpode haver algum, como no AMPHP, por exemplo.

Muitas vezes, técnicas para preencher o Promise resolvee rejectremover em um objeto separado a função assíncrona de estado de armazenamento adiado. Pode ser considerado como uma espécie de fábrica da Promise. É único: um adiado faz uma promessa.



Como aplicar isso no cliente SQL se decidirmos escrevê-lo nós mesmos?

Cliente SQL assíncrono


Primeiro, criamos o adiado, fizemos todo o trabalho com soquetes, anotamos os dados e retornamos o Promise - tudo é simples.

public function execAsync(string $query, array $params = [])
{
    $deferred = new Deferred;

    $socket = stream_socket_client('127.0.0.1:3306', ...);
    stream_set_blocking($socket, false);

    $data = $this->packBinarySQL($query, $params);
    socket_write($socket, $data, strlen($data));

    return $deferred->promise();
}

Quando temos o Promise, podemos, por exemplo:

  • defina o retorno de chamada e obtenha os affected rowsque retornam para nós PDOConnection;
  • lidar com o erro, adicione ao log;
  • Repita a consulta se o servidor SQL responder com um erro.

$promise = $this->execAsync($sql, $user->data());

$promise->onResolve(function (int $rows) {
    echo "Affected rows: {$rows}";
});

$promise->onReject(function (\Throwable $error) {
    log($error->getMessage());
});

A questão permanece: definimos o retorno de chamada, e quem ligará resolvee reject?

Loop de eventos


Existe o conceito de loop de eventos - um loop de eventos . Ele é capaz de processar mensagens em um ambiente assíncrono. Para E / S assíncrona, essas serão mensagens do sistema operacional que o soquete está pronto para ler ou gravar.

Como funciona.

  • O cliente diz ao Event Loop que está interessado em algum tipo de soquete.
  • O Loop de Eventos pesquisa o SO por meio de uma chamada do sistema stream_select: o soquete está pronto, todos os dados gravados, são os dados provenientes do outro lado.
  • Se o SO reportar que o soquete não está pronto, bloqueado, o Event Loop repetirá o loop.
  • Quando o sistema operacional notifica que o soquete está pronto, o Event Loop retorna o controle ao cliente e ativa ( resolveou reject) Promise.



Expressamos esse conceito no código: pegue o caso mais simples, remova o tratamento de erros e outras nuances, para que um loop infinito permaneça. Em cada iteração, ele pesquisará o SO sobre soquetes prontos para leitura ou gravação e chamará um retorno de chamada para um soquete específico.

public static function run()
{
    while (true) {
        stream_select($readSockets, $writeSockets, null, 0);
        
        foreach ($readSockets as $i => $socket) {
            call_user_func(self::readCallbacks[$i], $socket);
        }

        // Do same for write sockets
    }
}

Complementamos nosso cliente SQL. Informamos ao Event Loop que, assim que os dados do servidor SQL chegarem ao soquete com o qual estamos trabalhando, precisamos trazer o adiado para o estado "concluído" e transferir os dados do soquete para o Promise.

public function execAsync(string $query, array $params = [])
{
    $deferred = new Deferred;
    ...
    Loop::onReadable($socket, function ($socket) use ($deferred) {
        $deferred->resolve(socket_read($socket));
    });

    return $deferred->promise();
}

O Event Loop pode manipular nossa E / S e funciona com soquetes . O que mais ele pode fazer?

  • JavaScript setTimeout setInterval — . N . Event Loop .
  • Event Loop . process control, .

Event Loop


Escrever seu loop de eventos não é apenas possível, mas também necessário. Se você deseja trabalhar com PHP assíncrono, é importante escrever sua própria implementação simples para entender como isso funciona. Mas na produção, é claro, não o usaremos, mas tomaremos implementações prontas: estáveis, sem erros e comprovadas no trabalho.

Existem três implementações principais.

ReactPHP . O projeto mais antigo, iniciado no PHP 5.3. Agora a versão mínima exigida do PHP é 5.3.8. O projeto implementa o padrão Promises / A do mundo JavaScript.

AMPHP . É essa implementação que eu prefiro usar. O requisito mínimo é o PHP 7.0, e desde a próxima versão já é 7.3. Ele usa corotinas em cima do Promise.

Swoole. Essa é uma estrutura chinesa interessante, na qual os desenvolvedores tentam portar alguns conceitos do mundo Go para PHP. A documentação em inglês está incompleta, a maior parte no GitHub em chinês. Se você conhece o idioma, vá em frente, mas até agora estou com medo de trabalhar.



ReactPHP


Vamos ver como será o cliente usando o ReactPHP for MySQL.

$connection = (new ConnectionFactory)->createLazyConnection();

$promise = $connection->query('UPDATE users SET ...');
$promise->then(
    function (QueryResult $command) {
        echo count($command->resultRows) . ' row(s) in set.';
    },
    function (Exception $error) {
        echo 'Error: ' . $error->getMessage();
    });

Tudo é quase o mesmo que escrevemos: criamos onnectione executamos a solicitação. Podemos definir o retorno de chamada para processar os resultados (retorno affected rows):

    function (QueryResult $command) {
        echo count($command->resultRows) . ' row(s) in set.';
    },

e retorno de chamada para tratamento de erros:

    function (Exception $error) {
        echo 'Error: ' . $error->getMessage();
    });

A partir desses retornos de chamada, você pode construir cadeias longas, porque cada resultado thenno ReactPHP também retorna Promise.

$promise
    ->then(function ($data) {
        return new Promise(...);
    })
    ->then(function ($data) {
        ...
    }, function ($error) {
        log($error);
    })
    ...

Esta é uma solução para um problema chamado inferno de retorno de chamada. Infelizmente, na implementação do ReactPHP, isso leva ao problema "Inferno da promessa", quando são necessários 10 a 11 retornos de chamada para conectar corretamente o RabbitMQ . Trabalhar com esse código e corrigi-lo é difícil. Eu rapidamente percebi que isso não era meu e mudei para o AMPHP.

Amphp


Este projeto é mais novo que o ReactPHP e promove um conceito diferente - corotinas . Se você olhar para trabalhar com o MySQL no AMPHP, poderá ver que isso é quase o mesmo que trabalhar PDOConnectionno PHP.

$pool = Mysql\pool("host=127.0.0.1 port=3306 db=test");

try {
    $result = yield $pool->query("UPDATE users SET ...");

    echo $result->affectedRows . ' row(s) in set.';
} catch (\Throwable $error) {
    echo 'Error: ' . $error->getMessage();
}

Aqui, criamos um pool, conectamos e executamos a solicitação. Podemos lidar com erros através dos habituais try...catch, não precisamos de retornos de chamada.

Mas antes da chamada assíncrona, a palavra-chave - aparece aqui yield.

Geradores


A palavra-chave yieldtransforma nossa função em um gerador.

function generator($counter = 1)
{
    yield $counter++;

    echo "A";

    yield $counter;

    echo "B";

    yield ++$counter;
}

Assim que o intérprete PHP encontra yieldfunções no corpo, ele percebe que é uma função geradora. Em vez de executar, um objeto de classe é criado quando chamado Generator.

Geradores herdam a interface do iterador.

$generator = generator(1);

foreach ($generator as $value) {
    echo $value;
}

while ($generator->valid()) {
    echo $generator->current();

    $generator->next();
}

Assim, é possível executar ciclos foreache whileentre outros. Mas, mais interessante, o iterador possui métodos currente next. Vamos examiná-los passo a passo.

Execute nossa função generator($counter = 1). Chamamos o método gerador current(). O valor da variável será retornado $counter++.

Assim que executarmos o gerador next(), o código passará para a próxima chamada dentro do gerador yield. Todo o código entre os dois yieldserá executado, e isso é legal. Continuando a girar o gerador, obtemos o resultado.

Coroutines


Mas o gerador tem uma função mais interessante - podemos enviar dados para o gerador de fora. Nesse caso, isso não é exatamente um gerador, mas uma corotina ou corotina.

function printer() {  
    while (true) {     
        echo yield;       
    }                             
}                                

$print = printer();
$print->send('Hello');
$print->send(' PHPRussia');
$print->send(' 2019');
$print->send('!');

Nesta seção do código, é interessante que ele while (true)não bloqueie o fluxo de execução, mas será executado uma vez. Enviamos os dados para Corutin e os recebemos 'Hello'. Enviado mais - recebido 'PHPRussia'. O princípio é claro.

Além de enviar dados para o gerador, você pode enviar erros e processá-los por dentro, o que é conveniente.

function printer() {
    try {
        echo yield;
    } catch (\Throwable $e) {
        echo $e->getMessage();
    }
}

printer()->throw(new \Exception('Ooops...'));

Para resumir. Corutin é um componente de um programa que suporta parar e continuar a execução enquanto mantém o estado atual . Corutin se lembra de sua pilha de chamadas, dos dados contidos e pode usá-los no futuro.

Geradores e Promessa


Vamos dar uma olhada nas interfaces do gerador e do Promise.

class Generator
{
    public function send($data);
    public function throw(\Throwable $error);
}

class Promise
{
    public function resolve($data);
    public function reject(\Throwable $error);
}

Eles têm a mesma aparência, exceto por nomes de métodos diferentes. Podemos enviar dados e gerar um erro para o gerador e a Promise.

Como isso pode ser usado? Vamos escrever uma função.

function recoil(\Generator $generator)
{
    $promise = $generator->current();

    $promise->onResolve(function($data) use ($generator) {
        $generator->send($data);
        recoil($generator);
    };

    $promise->onReject(function ($error) use ($generator) {
        $generator->throw($error);
        recoil($generator);
    });
}

A função usa o valor atual do gerador: $promise = $generator->current();.

Eu exagerei um pouco. Sim, devemos verificar se o valor atual que nos é devolvido é algum tipo de instanceofpromessa. Nesse caso, podemos pedir a ele um retorno de chamada. Ele internamente envia os dados de volta ao gerador quando o Promise é bem-sucedido e inicia recursivamente a função recoil.

    $promise->onResolve(function($data) use ($generator) {
        $generator->send($data);
        recoil($generator);
    };

O mesmo pode ser feito com erros. Se o Promise falhar, por exemplo, o servidor SQL disse "Muitas conexões", podemos lançar o erro dentro do gerador e seguir para a próxima etapa.

Tudo isso nos leva ao importante conceito de multitarefa cooperativa.

Multitarefa cooperativa


Esse é um tipo de multitarefa, no qual a próxima tarefa é executada somente depois que a tarefa atual se declara explicitamente pronta para dar tempo ao processador para outras tarefas.

Eu raramente encontro algo simples, como trabalhar com apenas um banco de dados. Na maioria das vezes, no processo de atualização do usuário, você precisa atualizar os dados no banco de dados, no índice de pesquisa, limpar ou atualizar o cache e enviar mais 15 mensagens para o RabbitMQ. No PHP, tudo se parece com isso.



Realizamos operações uma a uma: atualizamos o banco de dados, o índice e o cache. Mas, por padrão, o PHP bloqueia essas operações (E / S); portanto, se você observar de perto, de fato, tudo está correto.



Nas partes escuras nós bloqueamos. Eles levam mais tempo.

Se trabalharmos no modo assíncrono, essas partes não estarão lá, a linha do tempo de execução será intermitente.



Você pode colar tudo isso e fazer peças uma por uma.



Para que serve tudo isso? Se você observar o tamanho da linha do tempo, a princípio leva muito tempo, mas assim que a colamos, o aplicativo acelera.

O próprio conceito de Event Loop e multitarefa cooperativa há muito tempo é usado em várias aplicações: Nginx, Node.js, Memcached, Redis. Todos eles usam dentro do Event Loop e são criados com o mesmo princípio.

Desde que começamos a falar sobre os servidores web Nginx e Node.js., lembremos como o processamento de solicitações no PHP ocorre.

Processamento de solicitações em PHP


O navegador envia uma solicitação, chega ao servidor HTTP atrás do qual há um pool de fluxos FPM. Um dos threads coloca esse pedido em operação, conecta nosso código e começa a executá-lo.



Quando a próxima solicitação chegar, outro encadeamento do FPM irá buscá-la, conectar o código e será executado.

Existem vantagens nesse esquema de trabalho .

  • Tratamento simples de erros . Se algo der errado e uma das solicitações falhar, não precisamos fazer nada - a próxima virá, e isso não afetará seu trabalho.
  • Nós não pensamos em memória . Não precisamos limpar ou monitorar a memória. Na próxima solicitação, toda a memória será limpa.

Este é um esquema interessante que funcionou em PHP desde o início e ainda funciona com sucesso. Mas também há desvantagens .

  • Limite o número de processos . Se tivermos 50 threads de FPM no servidor, assim que a 51ª solicitação chegar, ele aguardará até que um dos threads fique livre.
  • Custos para troca de contexto . O SO alterna solicitações entre fluxos FPM. Essa operação no nível do processador é denominada Context Switch. É caro e executa um grande número de medidas. É necessário salvar todos os registros, a pilha de chamadas, tudo o que está no processador, mudar para outro processo, carregar seus registros e sua pilha de chamadas, executar algo lá novamente, alternar novamente, salvar novamente ... Por um longo tempo.

Vamos abordar a questão de maneira diferente - escreveremos um servidor HTTP no próprio PHP.

Servidor HTTP assíncrono




Pode ser feito. Já aprendemos a trabalhar com soquetes no modo sem bloqueio, e uma conexão HTTP é o mesmo soquete. Como será a aparência e o funcionamento?

Este é um exemplo de inicialização de servidores HTTP na estrutura AMPHP.

Loop::run(function () {
    $app = new Application();
    $app->bootstrap();

    $sockets = [Socket\listen('0.0.0.0:80')];

    $server = new Server($sockets, new CallableRequestHandler(
        function (Request $request) use ($app) {
            $response = yield $app->dispatch($request);

            return new Response(Status::OK, [], $response);
        })
    );

    yield $server->start();
});

Tudo é bem simples: carregue Applicatione crie um pool de soquetes (um ou mais).

Em seguida, iniciamos nosso servidor, configuramos-o Handler, que será executado em cada solicitação e enviamos a solicitação ao nosso Applicationpara obter uma resposta.

A última coisa a fazer é iniciar o servidor yield $server->start();.

No ReactPHP, ele terá a mesma aparência, mas somente haverá 150 retornos de chamada para opções diferentes, o que não é muito conveniente.

Problemas


Existem vários problemas com a assincronia no PHP.

Falta de padrões . Cada estrutura: Swoole, ReactPHP ou AMPHP implementa sua própria interface Promise e elas são incompatíveis.

Teoricamente, o AMPHP poderia interagir com o Promise do ReactPHP, mas há uma ressalva. Se o código para o ReactPHP não estiver muito bem escrito e, em algum lugar, implicitamente chamar ou criar um loop de eventos, acontece que dois Loops de Eventos girarão dentro.

O JavaScript possui um padrão Promises / A + relativamente bom que implementa o Guzzle. Seria bom se as estruturas o seguissem. Mas até agora isso não é.

Perdas de memória. Quando trabalhamos em PHP no modo FPM usual, podemos não pensar em memória. Mesmo que os desenvolvedores de alguma extensão tenham esquecido de escrever um bom código, esquecido de executar o Valgrind e em algum lugar dentro dos fluxos de memória, tudo bem - a próxima solicitação será limpa e será iniciada novamente. Mas no modo assíncrono, você não pode permitir isso, porque mais cedo ou mais tarde simplesmente cairemos OutOfMemoryException.

É possível reparar, mas é difícil e doloroso. Em alguns casos, o Xdebug ajuda, em outros, a analisar os erros que causaram OutOfMemoryException.

Operações de bloqueio . É vital não bloquear o loop de eventos quando escrevemos código assíncrono. O aplicativo fica mais lento assim que bloqueamos o fluxo de execução, cada uma das nossas rotinas começa a correr mais devagar.

O pacote kelunik / loop-block ajudará a encontrar essas operações para o AMPHP . Ele ajusta o cronômetro para um intervalo muito pequeno. Se o cronômetro não funcionar, estamos bloqueados em algum lugar. O pacote ajuda a encontrar locais de bloqueio, mas nem sempre: o bloqueio em algumas extensões pode não ser percebido.

Suporte de biblioteca: Cassandra, Influx, ClickHouse . O principal problema de todo o PHP assíncrono é o suporte de bibliotecas. Nós não podemos usar os habituais PDOConnection, RedisClientoutros drivers para todos - precisamos implementações sem bloqueio. Eles também devem ser escritos em PHP no modo sem bloqueio, porque os drivers C raramente fornecem interfaces que podem ser integradas ao código assíncrono.

A experiência mais estranha que tive com o driver do banco de dados Cassandra. Eles fornecem operaçõesExecuteAsync, GetAsynce outros, mas ao mesmo tempo eles retornam um objeto Futurecom um único método getque bloqueia. Há uma oportunidade de obter algo de forma assíncrona, mas, para aguardar o resultado, ainda bloquearemos todo o nosso loop. Para fazer isso de alguma maneira diferente, por exemplo, através de retornos de chamada, não funciona. Até escrevi meu cliente para Cassandra, porque o usamos em nosso trabalho.

Indicação de tipo . Este é um problema do AMPHP e corutin.

class UserRepository
{
    public function find(int $id): \Generator
    {
        $data = yield $this->db->query('SELECT ...', $id);

        return User::fill($data);
    }
}

Se ocorrer em uma função yield, torna-se um gerador. Neste ponto, não podemos mais especificar os tipos de dados de retorno corretos.

PHP 8


O que nos espera no PHP 8? Vou falar sobre minhas suposições ou, melhor dizendo, meus desejos ( nota do editor: Dmitry Stogov sabe o que realmente aparecerá no PHP 8 ).

Loop de Eventos Há uma chance de que ele apareça, porque está em andamento o trabalho de trazer o Event Loop de alguma forma para o kernel. Se isso acontecer, teremos uma função await, como em JavaScript ou C #, que nos permitirá aguardar o resultado da operação assíncrona em um determinado local. Nesse caso, não precisaremos de extensões, tudo funcionará de forma assíncrona no nível do kernel.


class UserRepository
{
    public function find(int $id): Promise<User>
    {
        $data = await $this->db->query('SELECT ...', $id);

        return User::fill($data);
    }
}


Genéricos Go está esperando por genéricos, estamos esperando por genéricos, todo mundo está esperando por genéricos.

class UserRepository
{
    public function find(int $id): Promise<User>
    {
        $data = yield $this->db->query('SELECT ...', $id);

        return User::fill($data);
    }
}

Mas não estamos esperando genéricos para coleções, mas para indicar que o resultado do Promise será exatamente o objeto Usuário.

Por que tudo isso?

Para velocidade e desempenho.
PHP é uma linguagem na qual a maioria das operações é vinculada a E / S. Raramente escrevemos um código que está significativamente associado aos cálculos no processador. Provavelmente, trabalhamos com soquetes: precisamos fazer uma solicitação ao banco de dados, ler algo, retornar uma resposta, enviar um arquivo. A assincronia permite que você acelere esse código. Se observarmos o tempo médio de resposta para 1000 solicitações, podemos acelerar em cerca de 8 vezes e em 10.000 solicitações em quase 6!

Em 13 de maio de 2020, nos reuniremos pela segunda vez no PHP Rússia para discutir a linguagem, bibliotecas e estruturas, maneiras de aumentar a produtividade e as armadilhas das soluções de hype. Aceitamos os quatro primeiros relatórios , mas a Chamada de Trabalhos ainda está chegando. Inscreva- se se desejar compartilhar sua experiência com a comunidade.

Source: https://habr.com/ru/post/undefined/


All Articles