Processamento assíncrono ou multitarefa em PHP

Cenário

Esta postagem foi originada do meu blog pessoal, em http://www.mullie.eu/parallel-processing-multi-tasking-php/

Em PHP, existem várias maneiras de processar dados de forma assíncrona, embora nenhuma funcione em todos os ambientes. Não existe uma solução verdadeira e a que melhor se adequar a você se resumirá principalmente à sua tarefa específica.

Embora tanto o multithreading quanto o multiprocessamento possam ser usados ​​para processar código em paralelo, provavelmente faz sentido primeiro distinguir entre os dois.

Fio

Para acelerar a execução de várias tarefas, faz sentido dividir o trabalho em vários threads, cada um executando uma tarefa menor. Em um multi-core ou em vários processadores, isso significa que vários processadores podem fazer uma parte do trabalho que precisa ser feito, ao mesmo tempo, em vez de concluir tudo sequencialmente, em um único thread de execução.

Threads são parte do mesmo processo e geralmente compartilham os mesmos recursos de memória e arquivo. Se não for devidamente contabilizado, isso pode levar a resultados inesperados, como condições de corrida ou impasses. No PHP, entretanto, este não será o caso: a memória não é compartilhada, embora ainda seja possível afetar os dados em outra thread.

pthreads

Documentos PHP

A única solução multithread no PHP é a extensão pthreads. Em sua forma mais simples, você escreveria um código como este para executar o trabalho de forma assíncrona:

class ChildThread extends Thread {
public $data;

public function run() {
/* Do some expensive work */

$this
->data = 'result of expensive work';
}
}

$thread
= new ChildThread();

if ($thread->start()) {
/*
* Do some expensive work, while already doing other

* work in the child thread.

*/


// wait until thread is finished
$thread
->join();

// we can now even access $thread->data
}

Os resultados obtidos por meio de processamento assíncrono na forma mais básica de threading, como essa, também podem ser obtidos por meio de multiprocessamento. Tudo o que fazemos aqui é apenas dividir o trabalho em 2 threads para, eventualmente, após a conclusão, processar o resultado do segundo thread no thread original. Threading realmente ganha uma vantagem sobre o multiprocessamento se for necessário transferir dados entre threads ou para manter a execução de várias etapas em ambas as threads em sincronia, via synchronized (), notificar () e esperar ().

pthreads é uma extensão PECL , compatível com um ZTS (Zend Thread Safe) PHP 5.3 e superior. Não faz parte do núcleo do PHP, então você terá que fazer pecl install pthreadsisso.

Para alguns exemplos avançados sobre como usar threading, verifique a página do GitHub .

Amp \ Thread

Docs

Amp \ Thread é uma implementação particularmente interessante de pthreads junto com sua estrutura multitarefa assíncrona Amphp .

O legal desse projeto é que ele esconde o complexo trabalho assíncrono por trás de uma interface baseada em promessas, como:

function expensiveWork() {
/* Do some expensive work */

return 'result of expensive work';
}

$dispatcher
= new Amp\Thread\Dispatcher;

// call 2 expensive functions to be executed asynchronously
$promise1
= $dispatcher->call('expensiveWork');
$promise2
= $dispatcher->call('expensiveWork');

$comboPromise
= Amp\all([$promise1, $promise2]);
list
($result1, $result2) = $comboPromise->wait();

// $result1 & $result2 now contain the results of both threads

Amp / Thread é projetado especificamente para aplicativos CLI. Você precisará do PHP5.5 + e pthreads instalados.

Processo

Um processo é 1 execução de aplicativo independente. Embora um processo PHP possa gerar um segundo processo, ambos os processos ficarão completamente isolados e não compartilharão nenhuma memória ou identificador, tornando muito mais difícil sincronizar dados entre eles (embora, por exemplo, usando recursos externos, não seja completamente impossível).

pcntl_fork

Documentos PHP

A bifurcação de um processo resultará na clonagem da solicitação em uma réplica exata, embora com seu próprio espaço de endereço. Tanto o processo pai quanto o filho (bifurcado) serão exatamente os mesmos até o momento da bifurcação, por exemplo: quaisquer variáveis ​​até aquele ponto serão exatamente as mesmas em ambos os processos. Após a bifurcação, alterar o valor de uma variável em um processo não afeta o outro processo.

$var = 'one';

$pid
= pcntl_fork();

/*
* From this point on, the process has been forked (or

* $pid will be -1 in case of failure.)

*

* $pid will be a different value in parent & child process:

* * in parent: $pid will be the process id of the child

* * in child: $pid will be 0 (zero)

*

* We can define 2 separate code paths for both processes,

* using $pid.

*/


if ($pid === -1) {
exit; // failed to fork
} elseif ($pid === 0) {
// $pid = 0, this is the child thread

/*
* Existing variables will live in both processes,

* but changes will not affect other process.

*/

echo $var
; // will output 'one'
$var
= 'two'; // will not affect parent process

/* Do some expensive work */
} else {
// $pid != 0, this is the parent thread

/*
* Do some expensive work, while already doing other

* work in the child process.

*/


echo $var
; // will output 'one'
$var
= 'three'; // will not affect child process

// make sure the parent outlives the child process
pcntl_wait
($status);
}

Para processar dados em paralelo, o multiprocessamento pode ser uma solução perfeitamente válida. No entanto, não é um substituto individual para o multithreading: é uma técnica inteiramente separada e ambas são úteis para multitarefa. E embora o multithreading torne muito mais fácil sincronizar threads ou trocar dados de pai para filho, ele também pode ser realizado em multiprocessamento, manualmente, por meio de recursos externos (por exemplo, por meio de arquivos, bancos de dados, caches.). No entanto, tenha cuidado com os pedidos simultâneos!

Note que pcntl_fork não funcionará se o PHP estiver sendo executado como um módulo Apache, neste caso esta função não existirá!

Popen

Documentos PHP

Embora tenhamos visto 2 estratégias para dividir uma solicitação em 2 caminhos de execução diferentes (por meio de threading ou bifurcação), também poderíamos apenas lançar uma nova solicitação. Aqui também, será mais difícil se comunicar entre os processos pai e filho.

child.php

/*
* This is the child process, it'll be launched

* from the parent process.

*/


/* Do some expensive work */

parent.php

/*
* This is the process being called by the user.

* From here, we'll launch child process child.php

*/


// open child process
$child
= popen('php child.php', 'r');

/*
* Do some expensive work, while already doing other

* work in the child process.

*/


// get response from child (if any) as soon at it's ready:
$response
= stream_get_contents($child);

Este é um exemplo extremamente simples: o processo filho será iniciado sem qualquer contexto. No entanto, você também pode passar alguns parâmetros relevantes para o script filho. Por exemplo , passaria um nome de arquivo para o script filho, que poderia adicionar algum contexto para aquele script sobre o que exatamente ele deve processar.popen('php child.php -f filename.txt', 'r');

Após a chamada popen, o script pai retomará sua execução sem esperar a conclusão do processo filho. Ele apenas aguardará o processo filho até stream_get_contentsser chamado.

Se você quiser stream_get_contentsbloquear a execução do pai, entretanto, você pode adicionar . Obter tal fluxo não bloqueado antes de ser concluído resultará em apenas uma resposta parcial. Para ler a resposta completa, todos os ‘no fluxo filho devem ser concatenados até que retorne verdadeiro. Só então o pai sabe que o processo filho foi concluído.stream_set_blocking($child, 0)stream_get_contentsstream_get_contentsfeof($child)

Observe que os comandos de popen dependem do seu ambiente. Os binários ou caminhos instalados podem ser diferentes, especialmente entre os sistemas operacionais.

fopen / curl / fsockopen

Se você não tiver certeza do ambiente em que seu software será executado, popenpode não ser uma opção: os comandos desejados podem ser inexistentes ou limitados. Semelhante à popenabordagem, os aplicativos da web podem disparar processos filhos separados para o servidor da web que executa a solicitação atual.

Uma variedade de funções pode ser usada, todas com suas limitações:
* fopené a mais fácil de implementar, mas não funcionará se allow_url_fopentiver sido definido como falso,
* curlnão pode ser instalado em todos os ambientes,
* fsockopendeve sempre funcionar, independentemente de allow_url_fopen, mas é muito mais difícil de implementar, pois você terá que lidar com cabeçalhos brutos, tanto para a solicitação quanto para a resposta da criança.

Essa abordagem é muito semelhante à popensolução. Pois fopen, isso seria:

child.php

/*
* This is the child process, it'll be launched

* from the parent process.

*/


/* Do some expensive work */

parent.php

/*
* This is the process being called. From here,

* we'll launch child process child.php

*/


// open child process
$child
= fopen('http://'.$_SERVER['HTTP_HOST'].'/child.php', 'r');

/*
* Do some expensive work, while already doing other

* work in the child process.

*/


// get response from child (if any) as soon at it's ready:
$response
= stream_get_contents($child);