O PHP tem encadeamento?


130

Encontrei este pacote PECL chamado threads , mas ainda não há um lançamento. E nada está chegando no site PHP.


Alguém sabe se isso ( pcntl_fork()) funcionará se for chamado do Apache?
Josh K

Isso é incrivelmente antigo, mas eu tenho uma resposta que realmente fornece threading em php (veja os links abaixo).
Alec Gorge

Eles recomendam não chamar o fork de um ambiente de servidor. Eu não os culpo. No entanto, pcntl_fork parece ser as melhores soluções para threading PHP.
just_wes

Sim, você não precisa bifurcar um processo apache2 php.
andho 29/02

2
Use pthreads funciona como charme
Baba

Respostas:


40

Não há nada disponível que eu saiba. A próxima melhor coisa seria simplesmente fazer com que um script execute outro via CLI, mas isso é um pouco rudimentar. Dependendo do que você está tentando fazer e da complexidade, isso pode ou não ser uma opção.


1
Isso foi o que eu pensei. Vi várias postagens antigas dizendo não e nada no php.net, então esse foi o meu pensamento. Obrigado por confirmar.
Thomas Owens

2
Sim, esse pacote PECL é uma espécie de provocação - eu também o encontrei, mas nada aconteceu.
Wilco

180

No manual do PHP para a extensão pthreads :

O pthreads é uma API orientada a objetos que permite multi-threading para usuários no PHP. Ele inclui todas as ferramentas necessárias para criar aplicativos multithread direcionados à Web ou ao console. Os aplicativos PHP podem criar, ler, escrever, executar e sincronizar com Threads, Workers e Stackables.

Por incrível que pareça, é totalmente verdade. Hoje, o PHP pode multiencadear para aqueles que desejam experimentá-lo.

Na primeira versão do PHP4, em 22 de maio de 2000, o PHP foi enviado com uma arquitetura segura de encadeamento - uma maneira de executar várias instâncias de seu interpretador em encadeamentos separados em ambientes SAPI (API do servidor) com vários encadeamentos. Nos últimos 13 anos, o design dessa arquitetura foi mantido e avançado: desde então está em uso de produção nos maiores sites do mundo.

A segmentação na área de usuários nunca foi uma preocupação para a equipe do PHP e permanece como tal hoje. Você deve entender que, no mundo em que o PHP atua, já existe um método definido de dimensionamento - adicione hardware. Ao longo dos muitos anos em que o PHP existiu, o hardware ficou cada vez mais barato e, portanto, isso se tornou uma preocupação cada vez menor para a equipe do PHP. Enquanto estava ficando mais barato, também ficou muito mais poderoso; hoje, nossos telefones celulares e tablets possuem arquiteturas de núcleo duplo e quádruplo e muita memória RAM, nossos desktops e servidores geralmente têm 8 ou 16 núcleos, 16 e 32 gigabytes de RAM, embora nem sempre possamos ter dois dentro do orçamento e ter dois computadores raramente é útil para a maioria de nós.

Além disso, o PHP foi escrito para os não programadores, pois é um idioma nativo para muitos entusiastas. A razão pela qual o PHP é tão facilmente adotado é porque é uma linguagem fácil de aprender e escrever. A razão pela qual o PHP é tão confiável hoje em dia é por causa da grande quantidade de trabalho que é aplicada em seu design e de todas as decisões tomadas pelo grupo PHP. É a confiabilidade e a grandeza que a mantêm à vista, depois de todos esses anos; onde seus rivais caíram no tempo ou na pressão.

A programação multithread não é fácil para a maioria, mesmo com a API mais coerente e confiável, há coisas diferentes em que pensar e muitos conceitos errados. O grupo PHP não deseja que o multi-threading do usuário seja um recurso principal, nunca recebeu muita atenção - e com razão. PHP não deve ser complexo para todos.

Considerando tudo, ainda há benefícios em permitir que o PHP utilize os recursos prontos e testados para produção, para permitir um meio de tirar o máximo proveito do que temos, ao adicionar mais nem sempre é uma opção e por muito nunca é realmente necessário.

O pthreads alcança, para aqueles que desejam explorá-lo, uma API que permite ao usuário aplicativos PHP multiencadeados. Sua API é um trabalho em andamento e designou um nível beta de estabilidade e integridade.

É do conhecimento geral que algumas das bibliotecas que o PHP usa não são seguras para threads, deve ficar claro para o programador que os pthreads não podem mudar isso e não tentam tentar. No entanto, qualquer biblioteca que seja segura para threads é utilizável, como em qualquer outra configuração segura para threads do intérprete.

O pthreads utiliza Threads Posix (mesmo no Windows), o que o programador cria são threads reais de execução, mas para que esses threads sejam úteis, eles devem estar cientes do PHP - capaz de executar código de usuário, compartilhar variáveis ​​e permitir um meio útil de comunicação ( sincronização ). Portanto, todo encadeamento é criado com uma instância do intérprete, mas, por design, ele é isolado de todas as outras instâncias do intérprete - assim como os ambientes da API do servidor com vários encadeamentos. O pthreads tenta preencher a lacuna de maneira sã e segura. Muitas das preocupações do programador de encadeamentos em C simplesmente não existem para o programador de pthreads; por design, pthreads é cópia na leitura e cópia na gravação (a RAM é barata); portanto, duas instâncias nunca manipulam os mesmos dados físicos. , mas ambos podem afetar os dados em outro encadeamento.

Por que copiar na leitura e copiar na gravação:

public function run() {
    ...
    (1) $this->data = $data;
    ...
    (2) $this->other = someOperation($this->data);
    ...
}

(3) echo preg_match($pattern, $replace, $thread->data);

(1) Enquanto um bloqueio de leitura e gravação é mantido no armazenamento de dados do objeto pthreads, os dados são copiados do local original na memória para o armazenamento de objetos. O pthreads não ajusta o refcount da variável, o Zend pode liberar os dados originais se não houver mais referências a ele.

(2) O argumento para someOperation faz referência ao armazenamento de objetos, os dados originais armazenados, que são uma cópia do resultado de (1), são copiados novamente para o mecanismo em um contêiner zval, enquanto isso ocorre em um bloqueio de leitura. o armazenamento de objetos, a trava é liberada e o mecanismo pode executar a função. Quando o zval é criado, ele possui uma refcount de 0, permitindo que o mecanismo libere a cópia na conclusão da operação, porque não existem outras referências a ele.

(3) O último argumento para preg_match faz referência ao armazenamento de dados, um bloqueio de leitura é obtido, os dados configurados em (1) são copiados para um zval, novamente com um refcount de 0. O bloqueio é liberado. A chamada para preg_match opera em uma cópia dos dados, que é ela mesma uma cópia dos dados originais.

Coisas a saber:

  • A tabela de hash do armazenamento de objetos em que os dados são armazenados, com segurança para threads, é
    baseada no TsHashTable enviado com PHP, pelo Zend.

  • O armazenamento de objetos tem um bloqueio de leitura e gravação, um bloqueio de acesso adicional é fornecido para o TsHashTable, de modo que, se requer (e exige var_dump / print_r, acesso direto às propriedades que o mecanismo PHP deseja referenciá-los), os pthreads podem manipular o TsHashTable fora da API definida.

  • Os bloqueios são mantidos apenas enquanto as operações de cópia ocorrem, quando as cópias foram feitas, os bloqueios são liberados, em uma ordem sensata.

Isso significa:

  • Quando ocorre uma gravação, não apenas um bloqueio de leitura e gravação é mantido, mas também um bloqueio de acesso adicional. A tabela em si está bloqueada, não há como outro contexto poder bloquear, ler, escrever ou afetá-la.

  • Quando ocorre uma leitura, não apenas o bloqueio de leitura é mantido, mas também o bloqueio de acesso adicional, novamente a tabela está bloqueada.

Dois contextos não podem acessar física nem simultaneamente os mesmos dados do armazenamento de objetos, mas as gravações feitas em qualquer contexto com uma referência afetarão os dados lidos em qualquer contexto com uma referência.

Isso não é arquitetura compartilhada e a única maneira de existir é coexistir. Aqueles que são um pouco mais experientes verão isso, há muitas cópias acontecendo aqui e se perguntam se isso é uma coisa boa. Muitas cópias são realizadas em um tempo de execução dinâmico, que é a dinâmica de uma linguagem dinâmica. O pthreads é implementado no nível do objeto, porque um bom controle pode ser obtido sobre um objeto, mas os métodos - o código que o programador executa - têm outro contexto, livre de bloqueios e cópias - o escopo do método local. O escopo do objeto no caso de um objeto pthreads deve ser tratado como uma maneira de compartilhar dados entre contextos, e é esse o objetivo. Com isso em mente, você pode adotar técnicas para evitar o bloqueio do armazenamento de objetos, a menos que seja necessário,

A maioria das bibliotecas e extensões disponíveis para PHP são invólucros finos em torno de terceiros; a principal funcionalidade do PHP é a mesma coisa. O pthreads não é um invólucro fino ao redor dos Posix Threads; é uma API de segmentação baseada em threads Posix. Não há sentido em implementar Threads no PHP que seus usuários não entendam ou não possam usar. Não há razão para que uma pessoa sem conhecimento do que é ou não um mutex não possa tirar proveito de tudo o que possui, tanto em termos de habilidade quanto de recursos. Um objeto funciona como um objeto, mas onde quer que dois contextos colidem, pthreads fornece estabilidade e segurança.

Qualquer pessoa que tenha trabalhado em java verá as semelhanças entre um objeto pthreads e o encadeamento em java, essas mesmas pessoas sem dúvida terão visto um erro chamado ConcurrentModificationException - pois soa um erro gerado pelo tempo de execução do java se dois threads gravam os mesmos dados físicos simultaneamente. Entendo por que ele existe, mas me deixa perplexo que, com recursos tão baratos quanto eles, juntamente com o fato de o tempo de execução ser capaz de detectar a simultaneidade no momento exato e único em que a segurança possa ser alcançada para o usuário, ele opte por gere um erro possivelmente fatal no tempo de execução, em vez de gerenciar a execução e o acesso aos dados.

Nenhum erro estúpido será emitido pelo pthreads; a API foi criada para tornar o encadeamento o mais estável e compatível possível, acredito.

Multi-threading não é como usar um novo banco de dados, deve-se prestar muita atenção a todas as palavras do manual e exemplos fornecidos com pthreads.

Por fim, no manual do PHP:

O pthreads foi e é um experimento com bons resultados. Qualquer uma de suas limitações ou recursos pode mudar a qualquer momento; essa é a natureza da experimentação. Suas limitações - geralmente impostas pela implementação - existem por um bom motivo; O objetivo do pthreads é fornecer uma solução utilizável para multitarefa em PHP em qualquer nível. No ambiente em que o pthreads é executado, são necessárias algumas restrições e limitações para fornecer um ambiente estável.


14
Sem sentido nenhum.
Joe Watkins

7
@GeoC. Eu nem tenho certeza de qual é o seu ponto aqui, isso é apenas um monte de bobagens e você não fornece razões , lógicas ou outras, para explicar por que você não concorda com nenhum argumento (do qual eu realmente não posso ver nenhum no post) .
perfil completo de Jimbo

13
@Tudor Eu não acho que você realmente saiba do que está falando, então fico feliz em ignorá-lo.
9138 Joe Watkins

4
@Tudor - muitos cientistas não "entenderam o ponto" de algo novo em seu ramo ou algo útil. A história mostrou que, na maioria das vezes, pessoas como você estão simplesmente erradas, é um fato. Assim como é fato, tudo o que você escreveu aqui é, na falta de uma palavra melhor, fezes. Eu sugiro fortemente, com tudo em mente positivo, que não participe de tópicos sobre os quais você não conhece nada (sendo este um).
NB

12
Coisa engraçada. Joe Watkins é o autor de pthreads, e ainda assim Tudor tenta provar que ele está errado.
Hristo Valkanov 25/08/2015

48

Aqui está um exemplo do que Wilco sugeriu:

$cmd = 'nohup nice -n 10 /usr/bin/php -c /path/to/php.ini -f /path/to/php/file.php action=generate var1_id=23 var2_id=35 gen_id=535 > /path/to/log/file.log & echo $!';
$pid = shell_exec($cmd);

Basicamente, isso executa o script PHP na linha de comando, mas retorna imediatamente o PID e depois é executado em segundo plano. (O eco $! Garante que nada mais seja retornado além do PID.) Isso permite que seu script PHP continue ou saia, se você desejar. Quando usei isso, redirecionei o usuário para outra página, onde a cada 5 a 60 segundos é feita uma chamada AJAX para verificar se o relatório ainda está em execução. (Eu tenho uma tabela para armazenar o gen_id e o usuário ao qual ele está relacionado.) O script de verificação executa o seguinte:

exec('ps ' . $pid , $processState);
if (count($processState) < 2) {
     // less than 2 rows in the ps, therefore report is complete
}

Há um breve post sobre esta técnica aqui: http://nsaunders.wordpress.com/2007/01/12/running-a-background-process-in-php/


Tenho um pequeno problema, como você verifica o status do processo em segundo plano? u pode me ilumine
Slier

25

Resumindo: sim, há multithreading no php, mas você deve usar o multiprocessamento.

Informações do Backgroud: threads vs. processos

Sempre há um pouco de confusão sobre a distinção de threads e processos, então, em breve, descreverei os dois:

  • Um encadeamento é uma sequência de comandos que a CPU processará. Os únicos dados em que consiste é um contador de programa. Cada núcleo da CPU processa apenas um encadeamento por vez, mas pode alternar entre a execução de diferentes por agendamento.
  • Um processo é um conjunto de recursos compartilhados. Isso significa que consiste em uma parte da memória, variáveis, instâncias de objetos, identificadores de arquivos, mutexes, conexões com o banco de dados e assim por diante. Cada processo também contém um ou mais threads. Todos os threads do mesmo processo compartilham seus recursos; portanto, você pode usar uma variável em um thread que você criou em outro. Se esses encadeamentos fizerem parte de dois processos diferentes, eles não poderão acessar diretamente os recursos uns dos outros. Nesse caso, você precisa de comunicação entre processos através de, por exemplo, tubos, arquivos, soquetes ...

Multiprocessamento

Você pode obter computação paralela criando novos processos (que também contêm um novo thread) com php. Se seus encadeamentos não precisarem de muita comunicação ou sincronização, a escolha é sua, pois os processos são isolados e não podem interferir no trabalho um do outro. Mesmo se um travar, isso não diz respeito aos outros. Se você precisar de muita comunicação, continue lendo em "multithreading" ou - infelizmente - considere usar outra linguagem de programação, porque a comunicação e a sincronização entre processos introduzem muita tez.

No php, você tem duas maneiras de criar um novo processo:

deixe o sistema operacional fazer isso por você : você pode dizer ao seu sistema operacional para criar um novo processo e executar um novo (ou o mesmo) script php nele.

  • para linux, você pode usar o seguinte ou considerar a resposta de Darryl Hein :

    $cmd = 'nice php script.php 2>&1 & echo $!';
    pclose(popen($cmd, 'r'));
    
  • para windows você pode usar isso:

    $cmd = 'start "processname" /MIN /belownormal cmd /c "script.php 2>&1"';
    pclose(popen($cmd, 'r'));
    

faça você mesmo com um fork : o php também oferece a possibilidade de usar bifurcação através da função pcntl_fork () . Um bom tutorial sobre como fazer isso pode ser encontrado aqui, mas eu recomendo fortemente não usá-lo, pois o fork é um crime contra a humanidade e especialmente contra oop.

Multithreading

Com o multithreading, todos os seus threads compartilham seus recursos para que você possa se comunicar facilmente e sincronizá-los sem muita sobrecarga. Por outro lado, você precisa saber o que está fazendo, pois as condições e os impasses da corrida são fáceis de produzir, mas muito difíceis de depurar.

O php padrão não fornece multithreading, mas existe uma extensão (experimental) que realmente fornece - pthreads . Sua documentação da API chegou ao php.net . Com ele, você pode fazer algumas coisas em linguagens de programação reais :-) assim:

class MyThread extends Thread {
    public function run(){
        //do something time consuming
    }
}

$t = new MyThread();
if($t->start()){
    while($t->isRunning()){
        echo ".";
        usleep(100);
    }
    $t->join();
}

Para linux, há um guia de instalação aqui no stackoverflow.

Para o Windows, existe um agora:

  • Primeiro você precisa da versão thread-safe do php.
  • Você precisa das versões pré-compiladas do pthreads e de sua extensão php. Eles podem ser baixados aqui . Certifique-se de baixar a versão compatível com a sua versão php.
  • Copie o php_pthreads.dll (do zip que você acabou de baixar) para a pasta de extensão php ([phpDirectory] / ext).
  • Copie o pthreadVC2.dll para o diretório [phpDirectory] (a pasta raiz - não a pasta de extensão).
  • Edite [phpDirectory] /php.ini e insira a seguinte linha

    extension=php_pthreads.dll
  • Teste-o com o script acima com um pouco de sono ou algo ali onde está o comentário.

E agora o grande MAS : Embora isso realmente funcione, o php não foi feito originalmente para multithreading. Existe uma versão segura do thread do php e, a partir da v5.4, parece estar praticamente livre de erros, mas o uso do php em um ambiente com vários threads ainda é desencorajado no manual do php (mas talvez eles não tenham atualizado o manual em isso ainda). Um problema muito maior pode ser o fato de muitas extensões comuns não serem seguras para threads . Portanto, você pode obter threads com esta extensão php, mas as funções das quais você depende ainda não são seguras, portanto você provavelmente encontrará condições de corrida, impasses e assim por diante no código que você não escreveu.


5
Isso é terrivelmente errado, o artigo que você referenciou é de 2008. Se o PHP não estivesse seguro no thread, ele não teria encadeado módulos SAPI.
Joe Watkins

1
@ Joe: Tudo bem, eu mudei no núcleo é thread-safe, mas muitas extensões não são.
Francois Bourgeois

1
Grande quantidade ? Eu acho que você encontrará muito poucos, você encontrou a documentação, mas não conseguiu lê-la corretamente: Nota: Os marcados com * não são bibliotecas seguras para threads e não devem ser usados ​​com PHP como módulo de servidor no multi servidores Web do Windows com rosca (IIS, Netscape). Isso ainda não importa em ambientes Unix.
Joe Watkins

6
O PHP é muito seguro para threads e, por muitos anos, algumas das bibliotecas externas e algumas não estão incluídas, mas está bem documentada e é bastante óbvia. O pthreads cria threads tão seguros quanto os criados pelo zend em um sapi multiencadeado, eu sei disso, porque eu, sozinho, escrevi pthreads. Ele usa todas as APIs disponíveis expostas pelo PHP, assim como as APIs do servidor, não estou dizendo que é completamente estável, mas a imagem que você pintou está completamente errada e muito mal informada.
Joe Watkins

@ Joe: Quando o manual diz que isso não importa para ambientes Unix, eles estão se referindo ao fato de que no sistema Unix o apache usa vários processos e no Windows usa threads. Então, basicamente, eles estão dizendo "se você não está usando threads de qualquer maneira, não precisa se preocupar com extensões que não sejam seguras". Quando usamos threads com pthreads, é claro - também importa em ambientes Unix.
Francois Bourgeois

17

Você pode usar o pcntl_fork () para obter algo semelhante aos threads. Tecnicamente, são processos separados, portanto a comunicação entre os dois não é tão simples com threads, e acredito que não funcionará se o PHP for chamado pelo apache.


4
Estou usando com sucesso o pcntl_fork para paralelizar uma tarefa de importação de dados bastante massiva. Funciona muito bem, e eu trabalhei em cerca de uma hora. Há um pouco de uma curva de aprendizado, mas depois que você entende o que está acontecendo, é bem simples.
Frank Farmer

Frank, isso é com CLI php ou apache PHP?
Artem Russakovskii 15/09/09

@ Artigo: eu gostaria de saber também.
Josh K

5
@Frank Farmer está nos provocando ... assim como o pacote PECL.
1126 Roger Roger

1
Eu estava usando pcntl_fork com CLI. Eu nunca tentei isso no apache; isso parece arriscado. Mesmo na CLI, houve alguns problemas inesperados. Eu parecia estar com um problema em que, se uma criança fechava um identificador de banco de dados (porque terminou seu trabalho), também fechava a conexão para irmãos. Como os filhos são cópias dos pais, prepare-se para a estranheza. Desde então, reformulei meu código para gerar novos processos completamente separados via exec () - é mais limpo assim.
Frank Farmer


7

pcntl_fork()é o que você está procurando, mas seu processo não é bifurcado. então você terá o problema da troca de dados. Para resolvê-los, você pode usar as funções de semáforo phps ( http://www.php.net/manual/de/ref.sem.php ) as filas de mensagens podem ser um pouco mais fáceis para o início do que os segmentos de memória compartilhada.

De qualquer forma, uma estratégia que estou usando em uma estrutura da Web que estou desenvolvendo que carrega blocos intensivos em recursos de uma página da Web (provavelmente com solicitações externas) paralela: estou fazendo uma fila de tarefas para saber quais dados estou esperando e depois bifurco fora dos trabalhos para cada processo. uma vez feito, eles armazenam seus dados no cache apc sob uma chave única que o processo pai pode acessar. assim que todos os dados estiverem lá, eles continuam. Estou usando o simple usleep()to wait porque a comunicação entre processos não é possível no apache (as crianças perdem a conexão com os pais e se tornam zumbis ...). então isso me leva à última coisa: é importante matar todas as crianças! há também classes que processam os processos, mas mantêm os dados, eu não os examinei, mas o zend framework possui um, e eles geralmente fazem um código lento, mas confiável. Você pode encontrá-lo aqui: http://zendframework.com/manual/1.9/en/zendx.console.process.unix.overview.html eu acho que eles usam segmentos shm! bem, por último mas não menos importante, há um erro neste site do zend, um pequeno erro no exemplo.

while ($process1->isRunning() && $process2->isRunning()) {
    sleep(1);
}
should of course be:
while ($process1->isRunning() || $process2->isRunning()) {
    sleep(1);
}



5

Eu tenho uma classe de encadeamento PHP que está funcionando perfeitamente em um ambiente de produção há mais de dois anos.

EDIT: Agora está disponível como uma biblioteca de compositores e como parte do meu framework MVC, o Hazaar MVC.

Consulte: https://git.hazaarlabs.com/hazaar/hazaar-thread


E se, seguindo o seu exemplo, o programa em file.php, digamos, por exemplo, extinguir a existência de uma lista de uris de 10k sites e depois salvar o resultado em um arquivo CSV ... problema?
1313 Roger Roger

O subprocesso será executado como o mesmo usuário que o script servidor-web / pai. Portanto, ao gravar arquivos, você terá as mesmas considerações sobre permissões que normalmente faria. Se você tiver problemas para gravar arquivos, tente gravar em / tmp e, quando estiver funcionando, vá a partir daí.
Jamie Carl

1
O link agora está inoperante devido a um novo design
Tony

Adicionado à minha estrutura MVC agora. Veja: git.hazaarlabs.com/hazaar/hazaar-thread
Jamie Carl


1

Já ouviu falar appserverde techdivision?

É escrito em php e funciona como um servidor de aplicativos gerenciando multithreads para aplicativos php de alto tráfego. Ainda está na versão beta, mas muito promissor.


-3

Existe o recurso bastante obscuro, e que será preterido em breve, chamado ticks . A única coisa que eu já usei é permitir que um script capture SIGKILL (Ctrl + C) e feche normalmente.


3
Os tiques não são executados em paralelo. Essencialmente, após cada instrução, sua função de tick é executada. Enquanto sua função de tick está em execução, o código principal não está em execução.
Frank Farmer

1
ticks são necessários apenas para o manipulador de sinal ().
Nick
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.