Finestre.  Virus.  I Quaderni.  Internet.  ufficio.  Utilità.  Autisti

Sembra che gli sviluppatori PHP utilizzino raramente il parallelismo. Non parlerò della semplicità del codice sincrono, la programmazione a thread singolo è, ovviamente, più semplice e chiara, ma a volte un piccolo uso del parallelismo può portare a un notevole aumento delle prestazioni.

In questo articolo, daremo un'occhiata a come si può ottenere il multithreading in PHP usando l'estensione pthreads. Ciò richiederà una versione ZTS (Zend Thread Safety) di PHP 7.x installata, insieme all'estensione pthreads v3 installata. (Al momento della scrittura, in PHP 7.1, gli utenti dovranno installare dal ramo principale nel repository pthreads - vedere l'estensione di terze parti.)

Un piccolo chiarimento: pthreads v2 è per PHP 5.x e non è più supportato, pthreads v3 è per PHP 7.x ed è in fase di sviluppo attivo.

Dopo una simile digressione, mettiamoci subito al lavoro!

Gestire le attività una tantum

A volte si desidera elaborare attività una tantum in modo multi-thread (ad esempio, eseguendo alcune attività legate all'I/O). In tali casi, è possibile utilizzare la classe Thread per creare un nuovo thread ed eseguire alcune elaborazioni su un thread separato.

Per esempio:

$task = nuova classe estende Thread ( private $response; public function run() ( $content = file_get_contents("http://google.com"); preg_match("~ (.+)~", $content, $matches); $this->response = $matches; ) ); $task->start() && $task->join(); var_dump($task->response); // stringa (6) "Google"

Qui, il metodo run è la nostra elaborazione che verrà eseguita all'interno del nuovo thread. Quando viene chiamato Thread::start, viene generato un nuovo thread e viene chiamato il metodo run. Quindi uniamo il thread generato al thread principale chiamando Thread::join , che si bloccherà fino a quando il thread generato non avrà terminato l'esecuzione. Ciò garantisce che l'attività termini l'esecuzione prima che proviamo a produrre il risultato (che è memorizzato in $task->response).

Potrebbe non essere desiderabile inquinare una classe con responsabilità aggiuntive relative alla logica del thread (inclusa la responsabilità di definire un metodo run). Possiamo isolare tali classi derivando dalla classe Threaded. Quindi possono essere eseguiti all'interno di un altro thread:

Class Task estende Threaded ( public $response; public function someWork() ( $content = file_get_contents("http://google.com"); preg_match("~ (.+) ~", $content, $matches); $ this->response = $matches; ) ) $task = new Task; $thread = new class($task) estende Thread ( private $task; public function __construct(Threaded $task) ( $this->task = $task; ) public function run() ( $this->task->someWork( ); ) ); $thread->start() && $thread->join(); var_dump($attività->risposta);

Qualsiasi classe che deve essere eseguita su un thread separato, dovere ereditare dalla classe Threaded. Questo perché fornisce le funzionalità necessarie per eseguire l'elaborazione su thread diversi, oltre a sicurezza implicita e interfacce utili (come la sincronizzazione delle risorse).

Diamo un'occhiata alla gerarchia di classi fornita dall'estensione pthreads:

Threaded (implementa Traversable, Collectable) Thread Worker Volatile Pool

Abbiamo già coperto e imparato le basi delle classi Thread e Threaded, ora diamo un'occhiata alle altre tre (Worker , Volatile e Pool).

Riutilizzo del filo

L'avvio di un nuovo thread per ogni attività che deve essere parallelizzata è piuttosto costoso. Questo perché l'architettura "niente di comune" deve essere implementata nei pthread per ottenere il multithreading all'interno di PHP. Ciò significa che l'intero contesto di esecuzione dell'istanza corrente dell'interprete PHP (incluse ogni classe, interfaccia, tratto e funzione) deve essere copiato per ogni thread creato. Poiché ciò ha un notevole impatto sulle prestazioni, un thread dovrebbe sempre essere riutilizzato quando possibile. I thread possono essere riutilizzati in due modi: con Worker s o con Pool s.

La classe Worker viene utilizzata per eseguire una serie di attività in modo sincrono all'interno di un altro thread. Questo viene fatto creando una nuova istanza di Worker (che crea un nuovo thread) e quindi inserendo le attività nello stack di quel singolo thread (utilizzando Worker::stack).

Ecco un piccolo esempio:

Class Task estende Threaded ( private $value; public function __construct(int $i) ( $this->value = $i; ) public function run() ( usleep(250000); echo "Task: ($this->value) \n"; ) ) $lavoratore = new Lavoratore(); $lavoratore->inizio(); for ($i = 0; $i stack(new Task($i)); ) while ($worker->collect()); $lavoratore->arresto();

Nell'esempio precedente, 15 attività vengono inserite nello stack per un nuovo oggetto $worker tramite il metodo Worker::stack, quindi vengono elaborate nell'ordine in cui sono state inserite. Il metodo Worker::collect, come mostrato sopra, viene utilizzato per ripulire le attività non appena hanno terminato l'esecuzione. Con esso, all'interno del ciclo while, blocchiamo il thread principale fino a quando tutte le attività nello stack non vengono completate e fino a quando non vengono cancellate, prima di chiamare Worker::shutdown . La terminazione anticipata di un lavoratore (ovvero, mentre ci sono ancora attività da completare) bloccherà comunque il thread principale fino a quando tutte le attività non avranno completato la loro esecuzione, solo le attività non vengono raccolte in modo indesiderato (il che comporta perdite di memoria).

La classe Worker fornisce diversi altri metodi relativi al suo stack di attività, tra cui Worker::unstack per rimuovere l'ultima attività inserita e Worker::getStacked per ottenere il numero di attività nello stack di esecuzione. Lo stack del lavoratore contiene solo le attività che devono essere completate. Una volta che un'attività nello stack è stata completata, viene rimossa e collocata in uno stack (interno) separato per la Garbage Collection (utilizzando il metodo Worker::collect).

Un altro modo per riutilizzare un thread per molte attività è utilizzare un pool di thread (tramite la classe Pool). Il pool di thread usa un gruppo di Worker per consentire l'esecuzione delle attività contemporaneamente, in cui viene impostato il fattore di concorrenza (il numero di thread del pool su cui viene eseguito) quando viene creato il pool.

Adattiamo l'esempio precedente per utilizzare un pool di lavoratori:

Class Task estende Threaded ( private $value; public function __construct(int $i) ( $this->value = $i; ) public function run() ( usleep(250000); echo "Task: ($this->value) \n"; ) ) $pool = new Pool(4); for ($i = 0; $i submit(new Task($i)); ) while ($pool->collect()); $piscina->arresto();

Ci sono alcune differenze notevoli quando si utilizza un pool rispetto a un lavoratore. In primo luogo, il pool non deve essere avviato manualmente, inizia l'esecuzione delle attività non appena diventano disponibili. Secondo, noi Inviare compiti al pool, no metterli su una pila. Inoltre, la classe Pool non eredita da Threaded e pertanto non può essere passata ad altri thread (a differenza di Worker).

Come buona pratica, i lavoratori e i pool dovrebbero sempre ripulire le proprie attività non appena sono state completate e quindi terminarle manualmente. Anche i thread creati con la classe Thread devono essere collegati al thread padre.

pthreads e (non) mutabilità

L'ultima classe che toccheremo è Volatile , una nuova aggiunta a pthreads v3. La nozione di immutabilità è diventata un concetto importante nei pthread, poiché senza di essa le prestazioni diminuiscono in modo significativo. Pertanto, per impostazione predefinita, le proprietà delle classi Threaded che sono esse stesse oggetti Threaded sono ora immutabili e quindi non possono essere sovrascritte dopo che sono state originariamente assegnate. La mutabilità esplicita per tali proprietà è attualmente preferita e può ancora essere ottenuta con la nuova classe Volatile.

Diamo un'occhiata a un esempio che dimostra i nuovi vincoli di immutabilità:

Class Task estende Threaded // una classe Threaded ( funzione pubblica __construct() ( $this->data = new Threaded(); // $this->data non è sovrascrivibile, poiché è una proprietà Threaded di una classe Threaded ) ) $task = new class(new Task()) extends Thread ( // una classe Threaded, poiché Thread estende la funzione pubblica Threaded __construct($tm) ( $this->threadedMember = $tm; var_dump($this->threadedMember-> data); // oggetto(Threaded)#3 (0) () $this->threadedMember = new StdClass(); // non valido, poiché la proprietà è un membro Threaded di una classe Threaded ) );

Le proprietà Threaded delle classi Volatile, invece, sono mutabili:

Class Task estende Volatile ( public function __construct() ( $this->data = new Threaded(); $this->data = new StdClass(); // valido, dato che siamo in una classe volatile ) ) $task = new class(new Task()) estende Thread ( public function __construct($vm) ( $this->volatileMember = $vm; var_dump($this->volatileMember->data); // object(stdClass)#4 (0) () // ancora non valido, poiché Volatile estende Threaded, quindi la proprietà è ancora un membro Threaded di una classe Threaded $this->volatileMember = new StdClass(); ) );

Vediamo che la classe Volatile sovrascrive l'immutabilità imposta dalla classe genitore Threaded per fornire la possibilità di modificare le proprietà Threaded (così come unset() ).

C'è un altro argomento da discutere per coprire l'argomento della mutabilità e della classe Volatile: gli array. Nei pthread, gli array vengono automaticamente convertiti in oggetti volatili quando vengono assegnati a una proprietà della classe Threaded. Questo perché è semplicemente pericoloso manipolare un array da più contesti PHP.

Rivediamo l'esempio per capire meglio alcune cose:

$array = ; $task = new class($array) estende Thread ( private $data; public function __construct(array $array) ( $this->data = $array; ) public function run() ( $this->data = 4; $ questo->dati = 5; print_r($questo->dati); ) ); $task->start() && $task->join(); /* Output: oggetto volatile ( => 1 => 2 => 3 => 4 => 5) */

Vediamo che gli oggetti volatili possono essere trattati come se fossero array, poiché supportano operazioni di array come (come mostrato sopra) l'operatore di sottoinsieme (). Tuttavia, le classi Volatile non supportano le funzioni di matrice di base come array_pop e array_shift . Invece, la classe Threaded ci fornisce operazioni simili ai metodi incorporati.

Come dimostrazione:

$data = new class extends Volatile ( public $a = 1; public $b = 2; public $c = 3; ); var_dump($dati); var_dump($data->pop()); var_dump($data->shift()); var_dump($dati); /* Uscita: oggetto( [e-mail protetta])#1 (3) ( ["a"]=> int(1) ["b"]=> int(2) ["c"]=> int(3) ) int(3) int(1) oggetto ( [e-mail protetta])#1 (1) ( ["b"]=> int(2) ) */

Altre operazioni supportate includono Threaded::chunk e Threaded::merge .

Sincronizzazione

Nell'ultima sezione di questo articolo, esamineremo la sincronizzazione nei pthread. La sincronizzazione è un metodo che consente di controllare l'accesso alle risorse condivise.

Ad esempio, implementiamo un semplice contatore:

$contatore = nuova classe estende Thread ( public $i = 0; public function run() ( for ($i = 0; $i i; ) ) ); $contatore->inizio(); for ($i = 0; $i i; ) $contatore->join(); var_dump($contatore->i); // stampa un numero da 10 a 20

Senza l'uso della sincronizzazione, l'output non è deterministico. Più thread scrivono sulla stessa variabile senza accesso controllato, il che significa che gli aggiornamenti andranno persi.

Risolviamo questo in modo da ottenere l'output corretto di 20 aggiungendo una sincronizzazione:

$contatore = nuova classe estende Thread ( public $i = 0; public function run() ( $this->synchronized(function () ( for ($i = 0; $i i; ) )); ) ); $contatore->inizio(); $contatore->sincronizzato(funzione ($contatore) ( for ($i = 0; $i i; ) ), $contatore); $contatore->unisciti(); var_dump($contatore->i); // intero(20)

I blocchi di codice sincronizzati possono anche comunicare tra loro utilizzando i metodi Threaded::wait e Threaded::notify (o Threaded::notifyAll).

Ecco un incremento alternativo in due sincronizzati while loop:

$counter = new class extends Thread ( public $cond = 1; public function run() ( $this->synchronized(function () ( for ($i = 0; $i notify(); if ($this->cond === 1) ( $this->cond = 2; $this->wait(); ) ) )); ) ); $contatore->inizio(); $counter->synchronized(function ($counter) ( if ($counter->cond !== 2) ( $counter->wait(); // aspetta che l'altro inizi per primo ) for ($i = 10; $i notify(); if ($counter->cond === 2) ( $counter->cond = 1; $counter->wait(); ) ) ), $counter); $contatore->unisciti(); /* Output: int(0) int(10) int(1) int(11) int(2) int(12) int(3) int(13) int(4) int(14) int(5) int( 15) int(6) int(16) int(7) int(17) int(8) int(18) int(9) int(19) */

Potresti notare termini aggiuntivi, che sono stati posizionati attorno alla chiamata a Threaded::wait . Queste condizioni sono critiche perché consentono a un callback sincronizzato di riprendere quando ha ricevuto una notifica e la condizione specificata è true . Questo è importante perché le notifiche possono provenire da luoghi diversi da quando viene chiamato Threaded::notify. Pertanto, se le chiamate al metodo Threaded::wait non fossero racchiuse in condizioni, verrebbero eseguite false chiamate di sveglia, che porterà a un comportamento del codice imprevedibile.

Conclusione

Abbiamo esaminato le cinque classi nel pacchetto pthreads (Threaded , Thread , Worker , Volatile e Pool) e come viene utilizzata ciascuna classe. Abbiamo anche dato un'occhiata al nuovo concetto di immutabilità nei pthread breve recensione funzionalità di sincronizzazione supportate. Con queste nozioni di base, ora possiamo iniziare a esaminare come utilizzare i pthread nei casi del mondo reale! Questo sarà l'argomento del nostro prossimo post.

Se ti interessa la traduzione del prossimo post, fammelo sapere: commenta sul social. reti, votare e condividere il post con colleghi e amici.

  • programmazione,
  • Programmazione parallela
  • Di recente ho provato pthreads e sono rimasto piacevolmente sorpreso: questa è un'estensione che aggiunge la possibilità di lavorare con diversi thread reali a PHP. Nessuna emulazione, nessuna magia, nessun falso: tutto è reale.



    Sto esaminando questo problema. C'è un pool di attività che devono essere completate rapidamente. PHP ha altri strumenti per risolvere questo problema, non sono menzionati qui, l'articolo riguarda i pthread.



    Cosa sono i pthread

    È tutto! Beh, quasi tutto. In effetti, c'è qualcosa che può turbare il lettore curioso. Tutto questo non funziona per standard PHP compilato con le opzioni predefinite. Per goderti il ​​multithreading, devi avere ZTS (Zend Thread Safety) abilitato nel tuo PHP.

    Configurazione PHP

    Successivamente, PHP con ZTS. Ignora una così grande differenza nel tempo di esecuzione rispetto a PHP senza ZTS (37,65 vs 265,05 secondi), non ho provato a trovare un denominatore comune Impostazioni PHP. Nel caso senza ZTS, ad esempio ho abilitato XDebug.


    Come puoi vedere, quando si utilizzano 2 thread, la velocità di esecuzione del programma è circa 1,5 volte superiore rispetto al caso di un codice lineare. Quando si utilizzano 4 fili - 3 volte.


    Puoi notare che anche se il processore è a 8 core, il tempo di esecuzione del programma quasi non cambia se vengono utilizzati più di 4 thread. Sembra che ciò sia dovuto al fatto che il mio processore ha 4 core fisici, per chiarezza ho raffigurato una targa sotto forma di diagramma.


    Riepilogo

    PHP consente un multithreading abbastanza elegante usando l'estensione pthreads. Questo dà un notevole aumento delle prestazioni.

    Tag:

    • php
    • pthread
    Aggiungere etichette

    A volte diventa necessario eseguire più azioni contemporaneamente, ad esempio verificare la presenza di modifiche in una tabella del database e apportare modifiche a un'altra. Inoltre, se una delle operazioni (ad esempio il controllo delle modifiche) richiede molto tempo, è ovvio che l'esecuzione sequenziale non fornirà il bilanciamento delle risorse.

    Per risolvere tali problemi, la programmazione utilizza il multithreading: ogni operazione viene inserita in un thread separato con una quantità dedicata di risorse e funziona al suo interno. Con questo approccio, tutte le attività verranno eseguite separatamente e in modo indipendente.

    Sebbene PHP non supporti il ​​multithreading, esistono diversi metodi per emularlo, che verranno discussi di seguito.

    1. Esecuzione di più copie dello script: una copia per operazione

    //woman.php if (!isset($_GET["thread"])) ( system("wget ​​​​http://localhost/woman.php?thread=make_me_happy"); system("wget ​​​​http: //localhost/ woman.php?thread=make_me_rich"); ) elseif ($_GET["thread"] == "make_me_happy") ( make_her_happy(); ) elseif ($_GET["thread"] == "make_me_rich" ) ( trova_un altro_uno( ); )

    Quando eseguiamo questo script senza parametri, avvia automaticamente due copie di se stesso, con ID operazione ("thread=make_me_happy" e "thread=make_me_rich"), che avviano le funzioni richieste.

    Pertanto, otteniamo il risultato desiderato - due operazioni vengono eseguite contemporaneamente - ma ovviamente non si tratta di multithreading, ma semplicemente di una stampella per eseguire attività contemporaneamente.

    2. Path of the Jedi - utilizzando l'estensione PCNTL

    PCNTL è un'estensione che ti consente di lavorare completamente con i processi. Oltre alla gestione, supporta l'invio di messaggi, il controllo dello stato e l'impostazione delle priorità. Ecco come appare lo script precedente usando PCNTL:

    $pid = pcntl_fork(); if ($pid == 0) ( make_her_happy(); ) elseif ($pid > 0) ( $pid2 = pcntl_fork(); if ($pid2 == 0) ( find_another_one(); ) )

    Sembra piuttosto confuso, andiamo riga per riga.

    Nella prima riga, "fork" il processo corrente (fork - copiando il processo dal salvataggio dei valori di tutte le variabili), dividendolo in due processi (corrente e figlio) in esecuzione in parallelo.

    Per capire a che punto siamo questo momento, in un processo figlio o padre, pcntl_fork restituisce 0 per il figlio e l'ID del processo per il padre. Pertanto, nella seconda riga, guardiamo $pid, se è zero, allora siamo nel processo figlio - eseguiamo la funzione, altrimenti siamo nel genitore (riga 4), quindi creiamo un altro processo ed eseguiamo il compito allo stesso modo.

    Processo di esecuzione dello script:

    Pertanto, lo script crea altri 2 processi figlio, che ne sono copie, contengono le stesse variabili con valori simili. E con l'aiuto dell'identificatore restituito dalla funzione pcntl_fork, siamo guidati in quale flusso ci troviamo attualmente ed eseguiamo le azioni necessarie.



    Di recente ho provato pthreads e sono rimasto piacevolmente sorpreso: questa è un'estensione che aggiunge la possibilità di lavorare con diversi thread reali a PHP. Nessuna emulazione, nessuna magia, nessun falso: tutto è reale.



    Sto esaminando questo problema. C'è un pool di attività che devono essere completate rapidamente. PHP ha altri strumenti per risolvere questo problema, non sono menzionati qui, l'articolo riguarda i pthread.



    Cosa sono i pthread

    È tutto! Beh, quasi tutto. In effetti, c'è qualcosa che può turbare il lettore curioso. Niente di tutto questo funziona su PHP standard compilato con opzioni predefinite. Per goderti il ​​multithreading, devi avere ZTS (Zend Thread Safety) abilitato nel tuo PHP.

    Configurazione PHP

    Successivamente, PHP con ZTS. Ignora una così grande differenza nel tempo di esecuzione rispetto a PHP senza ZTS (37,65 vs 265,05 secondi), non ho provato a generalizzare la configurazione di PHP. Nel caso senza ZTS, ad esempio ho abilitato XDebug.


    Come puoi vedere, quando si utilizzano 2 thread, la velocità di esecuzione del programma è circa 1,5 volte superiore rispetto al caso di un codice lineare. Quando si utilizzano 4 fili - 3 volte.


    Puoi notare che anche se il processore è a 8 core, il tempo di esecuzione del programma quasi non cambia se vengono utilizzati più di 4 thread. Sembra che ciò sia dovuto al fatto che il mio processore ha 4 core fisici, per chiarezza ho raffigurato una targa sotto forma di diagramma.


    Riepilogo

    PHP consente un multithreading abbastanza elegante usando l'estensione pthreads. Questo dà un notevole aumento delle prestazioni.

    Di recente ho provato pthreads e sono rimasto piacevolmente sorpreso: questa è un'estensione che aggiunge la possibilità di lavorare con diversi thread reali a PHP. Nessuna emulazione, nessuna magia, nessun falso: tutto è reale.



    Sto esaminando questo problema. C'è un pool di attività che devono essere completate rapidamente. PHP ha altri strumenti per risolvere questo problema, non sono menzionati qui, l'articolo riguarda i pthread.



    Cosa sono i pthread

    È tutto! Beh, quasi tutto. In effetti, c'è qualcosa che può turbare il lettore curioso. Niente di tutto questo funziona su PHP standard compilato con opzioni predefinite. Per goderti il ​​multithreading, devi avere ZTS (Zend Thread Safety) abilitato nel tuo PHP.

    Configurazione PHP

    Successivamente, PHP con ZTS. Ignora una così grande differenza nel tempo di esecuzione rispetto a PHP senza ZTS (37,65 vs 265,05 secondi), non ho provato a generalizzare la configurazione di PHP. Nel caso senza ZTS, ad esempio ho abilitato XDebug.


    Come puoi vedere, quando si utilizzano 2 thread, la velocità di esecuzione del programma è circa 1,5 volte superiore rispetto al caso di un codice lineare. Quando si utilizzano 4 fili - 3 volte.


    Puoi notare che anche se il processore è a 8 core, il tempo di esecuzione del programma quasi non cambia se vengono utilizzati più di 4 thread. Sembra che ciò sia dovuto al fatto che il mio processore ha 4 core fisici, per chiarezza ho raffigurato una targa sotto forma di diagramma.


    Riepilogo

    PHP consente un multithreading abbastanza elegante usando l'estensione pthreads. Questo dà un notevole aumento delle prestazioni.

    Tag: aggiungi tag

    Se noti un errore, seleziona una parte di testo e premi Ctrl + Invio
    CONDIVIDERE: