Flertrådsprogrammering i PHP ved hjelp av Pthreads Translation. Multi-threaded databehandling i PHP: pthreads Path of the Jedi - ved hjelp av PCNTL-utvidelsen

Jeg prøvde nylig pthreads og ble positivt overrasket - det er en utvidelse som legger til muligheten til å jobbe med flere ekte tråder i PHP. Ingen emulering, ingen magi, ingen forfalskninger - alt er ekte.



Jeg vurderer en slik oppgave. Det er en pool av oppgaver som må løses raskt. PHP har andre verktøy for å løse dette problemet, de er ikke nevnt her, artikkelen handler om pthreads.



Hva er pthreads

Det er alt! Vel, nesten alt. Faktisk er det noe som kan opprøre en nysgjerrig leser. Ingenting av dette fungerer på standard PHP kompilert med standardalternativer. For å nyte multithreading må du ha ZTS (Zend Thread Safety) aktivert i PHP.

PHP oppsett

Deretter PHP med ZTS. Ikke ta hensyn til en så stor forskjell i utførelsestid sammenlignet med PHP uten ZTS (37,65 vs 265,05 sekunder), jeg prøvde ikke å redusere det til en fellesnevner PHP-innstillinger. I tilfelle uten ZTS har jeg XDebug aktivert for eksempel.


Som du kan se, når du bruker 2 tråder, er hastigheten på programutførelse omtrent 1,5 ganger høyere enn ved lineær kode. Ved bruk av 4 tråder - 3 ganger.


Du kan merke deg at selv om prosessoren er 8-kjerne, forble utføringstiden til programmet nesten uendret hvis mer enn 4 tråder ble brukt. Det ser ut til at dette skyldes det faktum at min prosessor har 4 fysiske kjerner. For klarhetens skyld har jeg avbildet platen i form av et diagram.


Sammendrag

I PHP er det mulig å jobbe ganske elegant med multithreading ved å bruke pthreads-utvidelsen. Dette gir en merkbar økning i produktiviteten.

Tags: Legg til tagger

Noen ganger blir det nødvendig å utføre flere handlinger samtidig, for eksempel å sjekke endringer i en databasetabell og gjøre endringer i en annen. Dessuten, hvis en av operasjonene (for eksempel sjekke endringer) tar mye tid, er det åpenbart at sekvensiell utførelse ikke vil sikre ressursbalansering.

For å løse denne typen problemer bruker programmering multithreading - hver operasjon er plassert i en egen tråd med en tildelt mengde ressurser og fungerer innenfor den. Med denne tilnærmingen vil alle oppgaver bli utført separat og uavhengig.

Selv om PHP ikke støtter multithreading, er det flere metoder for å emulere det, om hvilke vi vil snakke under.

1. Kjøre flere kopier av skriptet - én kopi per operasjon

//woman.php if (!isset($_GET["tråd"])) ( 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" ) ( finn_en annen_en( ); )

Når vi kjører dette skriptet uten parametere, kjører det automatisk to kopier av seg selv, med operasjons-IDer ("thread=make_me_happy" og "thread=make_me_rich"), som initierer utførelsen av de nødvendige funksjonene.

På denne måten oppnår vi ønsket resultat - to operasjoner utføres samtidig - men dette er selvfølgelig ikke multithreading, men bare en krykke for å utføre oppgaver samtidig.

2. Path of the Jedi - ved å bruke PCNTL-utvidelsen

PCNTL er en utvidelse som lar deg jobbe fullt ut med prosesser. I tillegg til administrasjon støtter den sending av meldinger, kontroll av status og prioritering. Slik ser det forrige skriptet med PCNTL ut:

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

Det ser ganske forvirrende ut, la oss gå gjennom det linje for linje.

I den første linjen "gaffel" vi den nåværende prosessen (gaffel kopierer en prosess mens verdiene til alle variabler bevares), og deler den inn i to prosesser (nåværende og underordnede) som kjører parallelt.

For å forstå hvor vi er dette øyeblikket, i en barne- eller morprosess, returnerer pcntl_fork-funksjonen 0 for barnet og prosess-IDen for mor. Derfor, i den andre linjen, ser vi på $pid, hvis den er null, er vi i barneprosessen - vi utfører funksjonen, ellers er vi i moren (linje 4), så lager vi en annen prosess og utføre oppgaven på samme måte.

Skriptkjøringsprosess:

Dermed oppretter skriptet ytterligere 2 underordnede prosesser, som er kopiene og inneholder de samme variablene med lignende verdier. Og ved å bruke identifikatoren som returneres av pcntl_fork-funksjonen, finner vi ut hvilken tråd vi er i og utfører de nødvendige handlingene.

Det virker, PHP utviklere parallellisme brukes sjelden. Jeg vil ikke snakke om enkelheten til synkron kode; enkelt-tråds programmering er selvfølgelig enklere og klarere, men noen ganger kan litt bruk av parallellitet gi en merkbar økning i ytelsen.

I denne artikkelen skal vi ta en titt på hvordan multithreading kan oppnås i PHP ved å bruke pthreads-utvidelsen. For å gjøre dette trenger du ZTS-versjonen (Zend Thread Safety) av PHP 7.x installert, sammen med installert utvidelse pthreads v3. (På skrivende stund, i PHP 7.1, må brukere installere fra hovedgrenen i pthreads-depotet - se tredjepartsutvidelse.)

En liten presisering: pthreads v2 er beregnet for PHP 5.x og støttes ikke lenger, pthreads v3 er for PHP 7.x og er under utvikling.

Etter en slik digresjon, la oss gå rett til poenget!

Behandling av engangsoppgaver

Noen ganger vil du behandle engangsoppgaver på en flertråds måte (for eksempel utføre en I/O-bundet oppgave). I slike tilfeller kan du bruke Thread-klassen til å lage en ny tråd og kjøre litt behandling på en egen tråd.

For eksempel:

$task = ny klasse utvider tråden (privat $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); // string (6) "Google"

Her er kjøringsmetoden vår behandling, som vil bli utført i en ny tråd. Når Thread::start kalles, opprettes en ny tråd og kjøremetoden kalles. Vi kobler deretter undertråden tilbake til hovedtråden ved å kalle Thread::join , som vil blokkere til undertråden er ferdig utført. Dette sikrer at oppgaven blir fullført før vi prøver å skrive ut resultatet (som er lagret i $task->response).

Det er kanskje ikke ønskelig å forurense en klasse med tilleggsansvar knyttet til flytlogikk (inkludert ansvaret for å definere en kjøremetode). Vi kan skille slike klasser ved å arve dem fra Threaded-klassen. Deretter kan de kjøres i en annen tråd:

Klasseoppgave utvider Threaded ( public $response; public function someWork() ( $content = file_get_contents("http://google.com"); preg_match("~ (.+) ~", $content, $matches); $ this->response = $matches; ) ) $task = ny oppgave; $thread = new class($task) utvider Thread ( privat $task; offentlig funksjon __construct(Threaded $task) ( $this->task = $task; ) public function run() ( $this->task->someWork( ); ) ); $thread->start() && $thread->join(); var_dump($oppgave->svar);

Enhver klasse som må kjøres i en egen tråd arv fra klassen Threaded. Dette er fordi det gir de nødvendige egenskapene til å utføre prosessering på forskjellige tråder, samt implisitt sikkerhet og nyttige grensesnitt (som ressurssynkronisering).

La oss ta en titt på klassehierarkiet som tilbys av pthreads-utvidelsen:

Gjenget (implementerer Traversable, Collectable) Thread Worker Volatile Pool

Vi har allerede dekket og lært det grunnleggende om Thread og Threaded-klassene, la oss nå ta en titt på de tre andre (Worker, Volatile og Pool).

Gjenbruk av tråder

Å starte en ny tråd for hver oppgave som må parallelliseres er ganske dyrt. Dette er fordi en common-nothing-arkitektur må implementeres i pthreads for å oppnå multithreading i PHP. Noe som betyr at hele utførelseskonteksten til den gjeldende forekomsten av PHP-tolken (inkludert hver klasse, grensesnitt, egenskap og funksjon) må kopieres for hver tråd som opprettes. Fordi dette har en merkbar ytelsespåvirkning, bør strømmen alltid gjenbrukes når det er mulig. Tråder kan gjenbrukes på to måter: ved å bruke Workers eller ved å bruke Pools.

Worker-klassen brukes til å utføre en rekke oppgaver synkront i en annen tråd. Dette gjøres ved å opprette en ny Worker-forekomst (som lager en ny tråd), og deretter skyve oppgaver over på den separate trådens stabel (ved å bruke Worker::stack).

Her er et lite eksempel:

Klasseoppgave utvider Threaded ( privat $verdi; offentlig funksjon __construct(int $i) ( $this->value = $i; ) public function run() ( usleep(250000); ekko "Oppgave: ($this->value) \n"; ) ) $arbeider = ny arbeider(); $worker->start(); for ($i = 0; $i stack(ny oppgave($i)); ) while ($worker->collect()); $worker->shutdown();

I eksemplet ovenfor blir 15 oppgaver for et nytt $worker-objekt skjøvet inn på stabelen via Worker::stack-metoden, og deretter behandles de i den rekkefølgen de ble skjøvet. Worker::collect-metoden, som vist ovenfor, brukes til å rydde opp i oppgaver så snart de er ferdige. Med den, inne i en while-løkke, blokkerer vi hovedtråden til alle oppgaver på stabelen er fullført og tømt - før vi kaller Worker::shutdown . Å avslutte en arbeider tidlig (dvs. mens det fortsatt er oppgaver som må fullføres) vil fortsatt blokkere hovedtråden til alle oppgavene har fullført sin utførelse, bare at oppgavene ikke blir søppelsamlet (som medfører representerer minnelekkasjer).

Worker-klassen gir flere andre metoder relatert til oppgavestabelen, inkludert Worker::unstack for å fjerne den siste stablede oppgaven og Worker::getStacked for å få antall oppgaver i utførelsesstabelen. En arbeiderstabel inneholder bare oppgavene som må utføres. Når en oppgave på stabelen er fullført, fjernes den og legges på en separat (intern) stabel for søppeloppsamling (ved bruk av Worker::collect-metoden).

En annen måte å gjenbruke en tråd på tvers av flere oppgaver er å bruke en trådpool (via Pool-klassen). En trådpool bruker en gruppe arbeidere for å gjøre det mulig å utføre oppgaver samtidig, der samtidighetsfaktoren (antall bassengtråder den opererer med) settes når bassenget opprettes.

La oss tilpasse eksemplet ovenfor for å bruke en gruppe arbeidere:

Klasseoppgave utvider Threaded ( privat $value; offentlig funksjon __construct(int $i) ( $this->value = $i; ) public function run() ( usleep(250000); ekko "Oppgave: ($this->value) \n"; ) ) $pool = ny Pool(4); for ($i = 0; $i submit(ny oppgave($i)); ) while ($pool->collect()); $pool->shutdown();

Det er noen få bemerkelsesverdige forskjeller når du bruker et basseng i motsetning til en arbeider. For det første trenger ikke bassenget å startes manuelt; det begynner å utføre oppgaver så snart de blir tilgjengelige. For det andre, vi sende oppgaver til bassenget, ikke legg dem på en stabel. I tillegg arver ikke Pool-klassen fra Threaded og kan derfor ikke overføres til andre tråder (i motsetning til Worker).

Hvordan god trening For arbeidere og bassenger bør du alltid rydde opp i oppgavene deres så snart de er fullført, og deretter manuelt avslutte dem selv. Tråder som er opprettet ved hjelp av tråd-klassen må også knyttes til overordnet tråd.

pthreads og (u)mutabilitet

Den siste klassen vi skal berøre er Volatile, et nytt tillegg til pthreads v3. Uforanderlighet har blitt et viktig begrep i pthreads fordi uten det lider ytelsen betydelig. Derfor, som standard, er egenskapene til Threaded-klasser som i seg selv er Threaded-objekter, nå uforanderlige, og derfor kan de ikke overskrives etter den første tilordningen. Eksplisitt mutabilitet for slike egenskaper er foreløpig foretrukket, og kan fortsatt oppnås ved å bruke den nye Volatile-klassen.

La oss se på et eksempel som vil demonstrere de nye uforanderlighetsbegrensningene:

Klasseoppgave utvider Threaded // en Threaded-klasse ( offentlig funksjon __construct() ( $this->data = new Threaded(); // $this->data kan ikke overskrives, siden det er en Threaded-egenskap for en Threaded-klasse ) ) $task = new class(new Task()) utvider Thread ( // en Threaded-klasse, siden Thread utvider Threaded public function __construct($tm) ( $this->threadedMember = $tm; var_dump($this->threadedMember-> data); // object(Threaded)#3 (0) () $this->threadedMember = new StdClass(); // ugyldig, siden egenskapen er et Threaded-medlem av en Threaded-klasse ) );

Gjengede egenskaper til Volatile-klasser er derimot mutbare:

Klasseoppgave utvider Volatile ( offentlig funksjon __construct() ( $this->data = new Threaded(); $this->data = new StdClass(); // gyldig, siden vi er i en flyktig klasse ) ) $task = new class(new Task()) utvider tråden (offentlig funksjon __construct($vm) ( $this->volatileMember = $vm; var_dump($this->volatileMember->data); // object(stdClass)#4 (0) () // fortsatt ugyldig, siden Volatile utvider Threaded, så egenskapen er fortsatt et Threaded-medlem av en Threaded-klasse $this->volatileMember = new StdClass(); ) );

Vi kan se at Volatile-klassen overstyrer uforanderligheten pålagt av dens overordnede Threaded-klasse for å gi muligheten til å endre Threaded-egenskaper (så vel som deaktivere() dem).

Det er et annet diskusjonsemne for å dekke emnet variasjon og klassen Volatile - arrays. I pthreads blir arrays automatisk kastet til Volatile objekter når de tilordnes til en egenskap i klassen Threaded. Dette er fordi det rett og slett ikke er trygt å manipulere en rekke PHP-kontekster.

La oss se på et eksempel igjen for å forstå noen ting bedre:

$array = ; $task = new class($array) utvider tråden (privat $data; offentlig funksjon __construct(array $array) ( $this->data = $array; ) public function run() ( $this->data = 4; $ this->data = 5; print_r($this->data); ) ); $task->start() && $task->join(); /* Output: Volatile Object ( => 1 => 2 => 3 => 4 => 5) */

Vi ser at flyktige objekter kan behandles som om de var arrays fordi de støtter array-operasjoner som (som vist ovenfor) subset()-operatoren. Volatile-klasser støtter imidlertid ikke grunnleggende array-funksjoner som array_pop og array_shift. I stedet gir Threaded-klassen oss slike operasjoner som innebygde metoder.

Som en demonstrasjon:

$data = ny klasse utvider Volatile ( offentlig $a = 1; offentlig $b = 2; offentlig $c = 3; ); var_dump($data); var_dump($data->pop()); var_dump($data->shift()); var_dump($data); /* Utdata: objekt(klasse@anonym)#1 (3) ( ["a"]=> int(1) ["b"]=> int(2) ["c"]=> int(3) ) int(3) int(1) objekt(klasse@anonym)#1 (1) ( ["b"]=> int(2) ) */

Andre støttede operasjoner inkluderer Threaded::chunk og Threaded::merge .

Synkronisering

I den siste delen av denne artikkelen skal vi se på synkronisering i pthreads. Synkronisering er en metode som lar deg kontrollere tilgang til delte ressurser.

La oss for eksempel implementere en enkel teller:

$counter = ny klasse utvider tråden ( offentlig $i = 0; public function run() ( for ($i = 0; $i i; ) ) ); $counter->start(); for ($i = 0; $i i; ) $counter->join(); var_dump($counter->i); // vil skrive ut et tall fra 10 til 20

Uten bruk av synkronisering er utgangen ikke deterministisk. Flere tråder skriver til samme variabel uten kontrollert tilgang, noe som betyr at oppdateringer vil gå tapt.

La oss fikse dette slik at vi får riktig utgang på 20 ved å legge til timing:

$counter = ny klasse utvider Tråd ( public $i = 0; public function run() ( $this->synchronized(function () (for ($i = 0; $i i; ))); ) ); $counter->start(); $counter->synchronized(funksjon ($counter) (for ($i = 0; $i i; ) ), $counter); $counter->join(); var_dump($counter->i); // int(20)

Synkroniserte kodeblokker kan også kommunisere med hverandre ved å bruke Threaded::wait og Threaded::notify (eller Threaded::notifyAll) metodene.

Her er en alternativ økning i to synkroniserte while-løkker:

$counter = ny klasse utvider tråden ( public $cond = 1; public function run() ( $this->synchronized(function () (for ($i = 0; $i notify(); if ($this->cond) === 1) ( $this->cond = 2; $this->wait(); ) ) )); ) ); $counter->start(); $counter->synchronized(function ($counter) ( if ($counter->cond !== 2) ( $counter->wait(); // vent til den andre starter først ) for ($i = 10; $i varsle(); if ($counter->cond === 2) ( $counter->cond = 1; $counter->wait(); ) ) ), $counter); $counter->join(); /* Utgang: 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) */

Du kan legge merke til flere forhold som har blitt plassert rundt anropet til Threaded::wait . Disse forholdene er kritiske fordi de lar den synkroniserte tilbakeringingen gjenopptas når den har mottatt et varsel og den angitte tilstanden er sann. Dette er viktig fordi varsler kan komme fra andre steder enn når Threaded::notify kalles. Derfor, hvis anrop til Threaded::wait-metoden ikke var omsluttet av betingelser, vil vi utføre falske vekkeanrop, som vil føre til uforutsigbar kodeatferd.

Konklusjon

Vi så på de fem klassene i pthreads-pakken (Threaded, Thread, Worker, Volatile og Pool) og hvordan hver klasse brukes. Vi tok også en titt på det nye konseptet med uforanderlighet i pthreads og ga en kort oversikt over de støttede synkroniseringsmulighetene. Med disse grunnleggende tingene på plass, kan vi nå begynne å se på hvordan pthreads kan brukes i virkelige tilfeller! Dette blir temaet for vårt neste innlegg.

Hvis du er interessert i oversettelsen av neste innlegg, gi meg beskjed: kommenter på sosiale medier. nettverk, stem opp og del innlegget med kolleger og venner.

  • programmering,
  • Parallell programmering
  • Jeg prøvde nylig pthreads og ble positivt overrasket - det er en utvidelse som legger til muligheten til å jobbe med flere ekte tråder i PHP. Ingen emulering, ingen magi, ingen forfalskninger - alt er ekte.



    Jeg vurderer en slik oppgave. Det er en pool av oppgaver som må løses raskt. PHP har andre verktøy for å løse dette problemet, de er ikke nevnt her, artikkelen handler om pthreads.



    Hva er pthreads

    Det er alt! Vel, nesten alt. Faktisk er det noe som kan opprøre en nysgjerrig leser. Ingenting av dette fungerer på standard PHP kompilert med standardalternativer. For å nyte multithreading må du ha ZTS (Zend Thread Safety) aktivert i PHP.

    PHP oppsett

    Deretter PHP med ZTS. Har ikke noe imot den store forskjellen i utførelsestid sammenlignet med PHP uten ZTS (37,65 vs 265,05 sekunder), jeg prøvde ikke å generalisere PHP-oppsettet. I tilfelle uten ZTS har jeg XDebug aktivert for eksempel.


    Som du kan se, når du bruker 2 tråder, er hastigheten på programutførelse omtrent 1,5 ganger høyere enn ved lineær kode. Ved bruk av 4 tråder - 3 ganger.


    Du kan merke deg at selv om prosessoren er 8-kjerne, forble utføringstiden til programmet nesten uendret hvis mer enn 4 tråder ble brukt. Det ser ut til at dette skyldes det faktum at min prosessor har 4 fysiske kjerner. For klarhetens skyld har jeg avbildet platen i form av et diagram.


    Sammendrag

    I PHP er det mulig å jobbe ganske elegant med multithreading ved å bruke pthreads-utvidelsen. Dette gir en merkbar økning i produktiviteten.

    Tagger:

    • php
    • pthreads
    Legg til merkelapper

    Jeg prøvde nylig pthreads og ble positivt overrasket - det er en utvidelse som legger til muligheten til å jobbe med flere ekte tråder i PHP. Ingen emulering, ingen magi, ingen forfalskninger - alt er ekte.



    Jeg vurderer en slik oppgave. Det er en pool av oppgaver som må løses raskt. PHP har andre verktøy for å løse dette problemet, de er ikke nevnt her, artikkelen handler om pthreads.



    Hva er pthreads

    Det er alt! Vel, nesten alt. Faktisk er det noe som kan opprøre en nysgjerrig leser. Ingenting av dette fungerer på standard PHP kompilert med standardalternativer. For å nyte multithreading må du ha ZTS (Zend Thread Safety) aktivert i PHP.

    PHP oppsett

    Deretter PHP med ZTS. Har ikke noe imot den store forskjellen i utførelsestid sammenlignet med PHP uten ZTS (37,65 vs 265,05 sekunder), jeg prøvde ikke å generalisere PHP-oppsettet. I tilfelle uten ZTS har jeg XDebug aktivert for eksempel.


    Som du kan se, når du bruker 2 tråder, er hastigheten på programutførelse omtrent 1,5 ganger høyere enn ved lineær kode. Ved bruk av 4 tråder - 3 ganger.


    Du kan merke deg at selv om prosessoren er 8-kjerne, forble utføringstiden til programmet nesten uendret hvis mer enn 4 tråder ble brukt. Det ser ut til at dette skyldes det faktum at min prosessor har 4 fysiske kjerner. For klarhetens skyld har jeg avbildet platen i form av et diagram.


    Sammendrag

    I PHP er det mulig å jobbe ganske elegant med multithreading ved å bruke pthreads-utvidelsen. Dette gir en merkbar økning i produktiviteten.



    
    Topp