Vytvára sa vlákno php postid. Viacvláknové programovanie v PHP s prekladom Pthreads

Niekedy je potrebné vykonať niekoľko akcií súčasne, napríklad skontrolovať zmeny v jednej databázovej tabuľke a vykonať úpravy v druhej. Navyše, ak jedna z operácií (napríklad kontrola zmien) trvá dlho, je zrejmé, že sekvenčné spustenie nezabezpečí vyváženie zdrojov.

Na vyriešenie tohto druhu problémov programovanie používa viacvláknové vlákno - každá operácia je umiestnená v samostatnom vlákne s vyhradeným množstvom zdrojov a pracuje v ňom. S týmto prístupom budú všetky úlohy vykonávané oddelene a nezávisle.

Napriek tomu, že PHP nepodporuje viacvláknové spracovanie, existuje niekoľko spôsobov jeho emulácie, o ktorých sa bude diskutovať nižšie.

1. Spustite viac kópií skriptu - jednu kópiu operácie

//woman.php if (! isset ($ _ GET ["thread"])) (system ("wget ​​http: //localhost/woman.php? thread = make_me_happy"); system ("wget ​​http : // localhost / woman.php? vlákno = make_me_rich ");) elseif ($ _GET [" vlákno "] ==" make_me_happy ") (make_her_happy ();) elseif ($ _GET [" vlákno]] == "make_me_rich ") (find_another_one ();)

Keď spustíme tento skript bez parametrov, automaticky spustí dve jeho kópie s ID operácie („vlákno = make_me_happy“ a „vlákno = make_me_rich“), ktoré iniciuje spustenie potrebných funkcií.

Dosiahneme tak požadovaný výsledok - dve operácie sa vykonávajú súčasne - ale samozrejme to nie je viacvláknové spracovanie, ale iba barlička na vykonávanie úloh súčasne.

2. Path of the Jedi - Using the PCNTL Extension

PCNTL je rozšírenie, ktoré vám umožní plne pracovať s procesmi. Okrem správy podporuje správy, kontrolu stavu a stanovovanie priorít. Takto vyzerá predchádzajúci skript pomocou PCNTL:

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

Vyzerá to dosť mätúco, poďme si to prejsť po riadkoch.

V prvom riadku „rozdvojíme“ aktuálny proces (fork - kopírovanie procesu zo zachovania hodnôt všetkých premenných) a rozdelíme ho na dva paralelne prebiehajúce procesy (súčasný a podradený).

Aby sme pochopili, kde sa nachádzame tento moment, v procese dieťaťa alebo rodiča, pcntl_fork vráti 0 pre dieťa a ID procesu pre rodiča. Preto sa v druhom riadku pozrieme na $ pid, ak sa rovná nule, potom sme v detskom procese - vykonávame funkciu, v opačnom prípade sme v rodičovskom procese (riadok 4), potom vytvoríme ďalší postup a úlohu vykonajte rovnakým spôsobom.

Proces spustenia skriptu:

Skript teda vytvorí ďalšie 2 podradené procesy, ktoré sú jeho kópiami a obsahujú rovnaké premenné s rovnakými hodnotami. A pomocou identifikátora vráteného funkciou pcntl_fork sme vedení tým, v ktorom vlákne sa práve nachádzame, a vykonáme potrebné akcie.

Nedávno som vyskúšal pthreads a bol som príjemne prekvapený - je to rozšírenie, ktoré pridáva možnosť pracovať s niekoľkými skutočnými vláknami v PHP. Žiadna emulácia, žiadna mágia, žiadne falzifikáty - všetko je skutočné.



Uvažujem nad takouto úlohou. Existuje množstvo úloh, ktoré je potrebné rýchlo dokončiť. PHP má ďalšie nástroje na riešenie tohto problému, nie sú tu uvedené, článok je o pthreads.



Čo je to pthreads

To je všetko! Takmer všetko. V skutočnosti existuje niečo, čo môže zvedavého čitateľa rozrušiť. Nič z toho nefunguje v štandardnom PHP kompilovanom s predvolenými možnosťami. Aby ste si mohli užiť viacvláknové vlákno, musíte mať vo svojom PHP povolený ZTS (Zend Thread Safety).

Nastavenie PHP

Ďalej PHP so ZTS. Nevadí veľký rozdiel v behu oproti PHP bez ZTS (37,65 vs 265,05 sekúnd), neskúšal som priniesť nastavenie PHP na spoločného menovateľa. V prípade bez ZTS mám napríklad povolený XDebug.


Ako vidíte, pri použití 2 vlákien je rýchlosť vykonávania programu asi 1,5 -krát vyššia ako v prípade lineárneho kódu. Pri použití 4 vlákien - 3 krát.


Môžete venovať pozornosť tomu, že hoci je procesor 8-jadrový, doba vykonania programu sa takmer nezmenila, ak boli použité viac ako 4 vlákna. Zdá sa, že je to kvôli skutočnosti, že fyzické jadrá môjho procesora sú 4. Kvôli prehľadnosti som nakreslil tanier vo forme diagramu.


Zhrnutie

PHP zvládne multithreading celkom elegantne pomocou rozšírenia pthreads. To prináša citeľné zvýšenie produktivity.

Značky: Pridajte značky

  • Programovanie,
  • Paralelné programovanie
  • Nedávno som vyskúšal pthreads a bol som príjemne prekvapený - je to rozšírenie, ktoré pridáva možnosť pracovať s niekoľkými skutočnými vláknami v PHP. Žiadna emulácia, žiadna mágia, žiadne falzifikáty - všetko je skutočné.



    Uvažujem nad takouto úlohou. Existuje množstvo úloh, ktoré je potrebné rýchlo dokončiť. PHP má ďalšie nástroje na riešenie tohto problému, nie sú tu uvedené, článok je o pthreads.



    Čo je to pthreads

    To je všetko! Takmer všetko. V skutočnosti existuje niečo, čo môže zvedavého čitateľa rozrušiť. Nič z toho nefunguje v štandardnom PHP kompilovanom s predvolenými možnosťami. Aby ste si mohli užiť viacvláknové vlákno, musíte mať vo svojom PHP povolený ZTS (Zend Thread Safety).

    Nastavenie PHP

    Ďalej PHP so ZTS. Nevadí veľký rozdiel v behu oproti PHP bez ZTS (37,65 vs 265,05 sekúnd), neskúšal som priniesť nastavenie PHP na spoločného menovateľa. V prípade bez ZTS mám napríklad povolený XDebug.


    Ako vidíte, pri použití 2 vlákien je rýchlosť vykonávania programu asi 1,5 -krát vyššia ako v prípade lineárneho kódu. Pri použití 4 vlákien - 3 krát.


    Môžete venovať pozornosť tomu, že hoci je procesor 8-jadrový, doba vykonania programu sa takmer nezmenila, ak boli použité viac ako 4 vlákna. Zdá sa, že je to kvôli skutočnosti, že fyzické jadrá môjho procesora sú 4. Kvôli prehľadnosti som nakreslil tanier vo forme diagramu.


    Zhrnutie

    PHP zvládne multithreading celkom elegantne pomocou rozšírenia pthreads. To prináša citeľné zvýšenie produktivity.

    Tagy:

    • php
    • pthreads
    Pridať značky

    Zdá sa, že vývojári PHP používajú súbežnosť len zriedka. Nebudem hovoriť o jednoduchosti synchrónneho kódu, jednovláknové programovanie je samozrejme jednoduchšie a prehľadnejšie, ale niekedy malé využitie paralelnosti môže priniesť citeľné zvýšenie výkonu.

    V tomto článku sa pozrieme na to, ako je možné v PHP dosiahnuť viacvláknové spracovanie pomocou rozšírenia pthreads. To bude vyžadovať nainštalovaný ZTS (Zend Thread Safety) PHP 7.x spolu s nainštalované rozšírenie pthreads v3. (V čase písania tohto článku v PHP 7.1 budú používatelia musieť nainštalovať z hlavnej vetvy v úložisku pthreads - pozrite si rozšírenie od iného dodávateľa.)

    Malé objasnenie: pthreads v2 je určený pre PHP 5.x a už nie je podporovaný, pthreads v3 je pre PHP 7.x a aktívne sa vyvíja.

    Po takej odbočke sa hneď vrhnime na vec!

    Spracovanie jednorazových úloh

    Niekedy chcete spracovať jednorazové úlohy viacvláknovým spôsobom (napríklad vykonať nejakú úlohu viazanú na I / O). V takýchto prípadoch môžete použiť triedu Thread na vytvorenie nového vlákna a zahájenie určitého spracovania v samostatnom vlákne.

    Napríklad:

    $ task = new class extends 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); // string (6) „Google“

    Tu je metódou spustenia naše spracovanie, ktoré sa vykoná v rámci nového vlákna. Keď sa zavolá Thread :: start, založí sa nové vlákno a zavolá sa metóda run. Potom pripojíme vytvorené vlákno späť k hlavnému vláknu zavolaním Thread :: join, ktoré bude blokovať, kým spustené vlákno nedokončí svoje spustenie. To zaisťuje, že sa úloha dokončí skôr, ako sa pokúsime vygenerovať výsledok (ktorý je uložený v $ task-> response).

    Možno nie je žiaduce znečistiť triedu ďalšími zodpovednosťami spojenými s logikou toku (vrátane zodpovednosti za definovanie metódy spustenia). Takéto triedy môžeme rozlíšiť dedením z triedy Threaded. Potom ich možno spustiť v inom vlákne:

    Úloha triedy rozširuje vlákno (public $ response; public function someWork () ($ content = file_get_contents ("http://google.com"); preg_match ("~ (. +) ~", $ Content, $ matches); $ this-> response = $ matches;)) $ task = new Task; $ vlákno = nová trieda ($ úloha) rozširuje vlákno (súkromná $ úloha; verejná funkcia __construct (vlákno $ úloha) ($ this-> úloha = $ úloha;) spustenie verejnej funkcie () ($ this-> task-> someWork ( );)); $ thread-> start () && $ thread-> join (); var_dump ($ task-> odpoveď);

    Každá trieda, ktorú je potrebné spustiť v samostatnom vlákne, by mal dediť z triedy Threaded. Dôvodom je, že poskytuje potrebné možnosti na spracovanie v rôznych vláknach, ako aj implicitné zabezpečenie a užitočné rozhrania (napríklad synchronizáciu zdrojov).

    Pozrime sa na hierarchiu tried, ktorú poskytuje rozšírenie pthreads:

    Vláknitý fond vlákien (implementuje traverzovateľné, zberateľské) vlákna

    Už sme si prebrali a naučili sa základy tried Thread a Threaded, teraz sa pozrime na ďalšie tri (Worker, Volatile a Pool).

    Opätovné použitie prúdov

    Začatie nového vlákna pre každú úlohu, ktorú je potrebné paralelizovať, je nákladné. Dôvodom je, že „nič-bežná“ architektúra musí byť implementovaná v pthreads, aby sa dosiahlo viacvláknové spracovanie v PHP. To znamená, že pre všetky vytvorené vlákna je potrebné skopírovať celý kontext spustenia aktuálnej inštancie tlmočníka PHP (vrátane každej triedy, rozhrania, znaku a funkcie). Pretože to má citeľný vplyv na výkon, stream by sa mal vždy znova použiť, kedykoľvek je to možné. Streamy je možné znova použiť dvoma spôsobmi: pomocou Workers alebo pomocou Pools.

    Trieda Worker sa používa na synchrónne vykonávanie niekoľkých úloh v rámci iného vlákna. To sa dosiahne vytvorením novej inštancie programu Worker (ktorá vytvorí nové vlákno) a následným odoslaním úloh do zásobníka tohto samostatného vlákna (pomocou balíka Worker :: stack).

    Tu je malý príklad:

    Úloha triedy rozširuje vlákno (súkromná hodnota $; verejná funkcia __construct (int $ i) ($ this-> hodnota = $ i;) spustenie verejnej funkcie () (usleep (250000); echo "Úloha: ($ táto-> hodnota) \ n ";)) $ worker = nový Worker (); $ pracovník-> štart (); pre ($ i = 0; $ i zásobník (nová úloha ($ i));) while ($ robot-> collect ()); $ worker-> shutdown ();

    Vo vyššie uvedenom príklade je 15 úloh vložených do zásobníka pre nový objekt $ worker prostredníctvom metódy Worker :: stack a potom sú spracované v poradí, v akom boli odoslané. Metóda Worker :: collect, ako je uvedené vyššie, sa používa na vyčistenie úloh hneď po dokončení vykonávania. Použitím v cykle while zablokujeme hlavné vlákno, kým sa nedokončia všetky úlohy zo zásobníka a kým nie sú vyčistené - než zavoláme Worker :: shutdown. Ukončenie práce pracovníka pred plánovaným termínom (to znamená, že stále existujú úlohy, ktoré je potrebné dokončiť) bude stále blokovať hlavné vlákno, kým sa všetky úlohy nedokončia, len úlohy nebudú odstránené smetiarom (čo znamená úniky pamäte).

    Trieda Worker ponúka niekoľko ďalších metód vzťahujúcich sa na zásobník úloh, vrátane Worker :: unstack na odstránenie poslednej pridanej úlohy a Worker :: getStacked na získanie počtu úloh v zásobníku spustenia. Zásobník pracovníka obsahuje iba úlohy, ktoré je potrebné dokončiť. Akonáhle je úloha zo zásobníka dokončená, je odstránená a umiestnená do samostatného (interného) zásobníka na zber odpadu (pomocou metódy Worker :: collect).

    Ďalším spôsobom, ako znova použiť vlákno na mnohé úlohy, je použitie fondu vlákien (prostredníctvom triedy Pool). Fond vlákien používa skupinu pracovníkov na povolenie vykonávania úloh súčasne, v ktorom je pri vytváraní fondu nastavený súbežný faktor (počet vlákien fondu, s ktorými funguje).

    Prispôsobme vyššie uvedený príklad tak, aby používal skupinu pracovníkov:

    Úloha triedy rozširuje vlákno (súkromná hodnota $; verejná funkcia __construct (int $ i) ($ this-> hodnota = $ i;) spustenie verejnej funkcie () (usleep (250000); echo "Úloha: ($ táto-> hodnota) \ n ";)) $ pool = nový Pool (4); for ($ i = 0; $ i submit (new Task ($ i));) while ($ pool-> collect ()); $ pool-> shutdown ();

    Pri použití skupiny na rozdiel od pracovníka existuje niekoľko pozoruhodných rozdielov. Po prvé, fond nie je potrebné spúšťať ručne; úlohy začne vykonávať hneď, ako budú k dispozícii. Za druhé, my poslaťúlohy do bazéna, nie ich uvedenie na stoh... Okrem toho trieda Pool nededí od Threaded, a preto nemôže byť odovzdaná iným vláknam (na rozdiel od Worker).

    Osvedčeným postupom pre pracovníkov a skupiny je, že by ste mali vždy upratať ich úlohy hneď, ako skončia, a potom ich ručne ukončiť sami. Vlákna vytvorené pomocou triedy Thread musia byť tiež pripojené k nadradenému vláknu.

    pthreads a (ne) mutovateľnosť

    Poslednou triedou, ktorej sa dotkneme, je Volatile, nový prírastok do programu pthreads v3. Pojem nemennosti sa stal dôležitým pojmom v pthreads, pretože bez neho výkon výrazne degraduje. Preto sú v predvolenom nastavení vlastnosti tried Threaded, ktoré sú samy o sebe závitovými objektmi, teraz nemenné, a preto ich nemožno po ich počiatočnom priradení prepísať. Výslovná mutabilita pre tieto vlastnosti je v súčasnosti preferovaná a stále ju možno dosiahnuť pomocou novej triedy prchavých látok.

    Pozrime sa na príklad, ktorý demonštruje nové obmedzenia nemennosti:

    Úloha triedy rozširuje triedu Threaded // a Threaded (verejná funkcia __construct () ($ this-> data = new Threaded (); // $ this-> údaje nie je možné prepísať, pretože ide o vláknovú vlastnosť triedy Threaded)) $ task = new class (new Task ()) extend Thread (// a Threaded class, since Thread extends Threaded public function __construct ($ tm) ($ this-> threadedMember = $ tm; var_dump ($ this-> threadedMember-> data); // object (Threaded) # 3 (0) () $ this-> threadedMember = new StdClass (); // invalid, since the property is a Threaded member of a Threaded class));

    Na druhej strane závitové vlastnosti prchavých tried sú premenlivé:

    Úloha triedy rozširuje volatilitu (verejná funkcia __construct () ($ this-> data = new Threaded (); $ this-> data = new StdClass (); // valid, since we are in volatile class)) $ task = new class (new Task ()) extend Thread (public function __construct ($ vm) ($ this-> volatileMember = $ vm; var_dump ($ this-> volatileMember-> data); // object (stdClass) # 4 (0) () // stále neplatné, pretože Volatile rozširuje Threaded, takže vlastnosť je stále Threaded členom Threaded triedy $ this-> volatileMember = new StdClass ();));

    Vidíme, že trieda Volatile prepíše nemennosť uloženú rodičovskou triedou Threaded, aby poskytovala schopnosť meniť vlastnosti Threaded (ako aj unset ()).

    Existuje ešte jedna téma diskusie na tému mutability a volatilnej triedy - polia. V pthreads sú polia automaticky prenášané do prchavých objektov, keď sú priradené k vlastnosti v triede Threaded. Dôvodom je, že jednoducho nie je bezpečné manipulovať s poľom z viacerých kontextov PHP.

    Pozrime sa znova na príklad, aby sme lepšie porozumeli niekoľkým veciam:

    $ pole =; $ task = new class ($ array) extend Thread (private $ data; public function __construct (array $ array) ($ this-> data = $ array;) public function run () ($ this-> data = 4; $ this-> data = 5; print_r ($ this-> data);)); $ task-> start () && $ task-> join (); / * Výstup: prchavý objekt (=> 1 => 2 => 3 => 4 => 5) * /

    Vidíme, že s prchavými objektmi je možné zaobchádzať ako s poliami, pretože podporujú operácie v poli, ako napríklad (ako je uvedené vyššie) operátor podmnožín (). Prchavé triedy však nepodporujú základné funkcie s poliami ako array_pop a array_shift. Namiesto toho nám trieda Threaded poskytuje podobné operácie ako vstavané metódy.

    Ako ukážka:

    $ data = new class extends Volatile (public $ a = 1; public $ b = 2; public $ c = 3;); var_dump ($ dáta); var_dump ($ data-> pop ()); var_dump ($ data-> shift ()); var_dump ($ dáta); / * Výstup: objekt ( [chránené e -mailom]) # 1 (3) (["a"] => int (1) ["b"] => int (2) ["c"] => int (3)) int (3) int (1) objekt ( [chránené e -mailom]) # 1 (1) (["b"] => int (2)) * /

    Medzi ďalšie podporované operácie patrí Threaded :: chunk a Threaded :: merge.

    Synchronizácia

    V poslednej časti tohto článku sa pozrieme na synchronizáciu v pthreads. Synchronizácia je technika, ktorá vám umožňuje ovládať prístup k zdieľaným zdrojom.

    Implementujme napríklad najjednoduchšie počítadlo:

    $ counter = new class extends Thread (public $ i = 0; public function run () (for ($ i = 0; $ i i;))); $ počítadlo-> štart (); pre ($ i = 0; $ i i;) $ counter-> join (); var_dump ($ counter-> i); // vytlačí číslo od 10 do 20

    Bez použitia synchronizácie nie je výstup deterministický. Viacero vlákien zapisuje do tej istej premennej bez riadeného prístupu, čo znamená, že dôjde k strate aktualizácií.

    Opravme to tak, aby sme získali správny výstup 20 pridaním synchronizácie:

    $ counter = new class extends Thread (public $ i = 0; public function run () ($ this-> synchronized (function () (for ($ i = 0; $ i i;; i)));)); $ počítadlo-> štart (); $ počítadlo-> synchronizované (funkcia ($ počítadlo) (pre ($ i = 0; $ i i;)), $ počítadlo); $ counter-> join (); var_dump ($ counter-> i); // int (20)

    Synchronizované bloky kódu môžu navzájom interagovať aj pomocou metód Threaded :: wait a Threaded :: Notify (alebo Threaded :: NotifyAll).

    Tu je sekvenčný prírastok v dvoch synchronizovaných slučkách:

    $ counter = nová trieda rozširuje vlákno (public $ cond = 1; public function run () ($ this-> synchronized (function () (for ($ i = 0; $ i notification (); if ($ this-> cond === 1) ($ this-> cond = 2; $ this-> wait ();))));)); $ počítadlo-> štart (); $ counter-> synchronizované (funkcia ($ counter) (ak ($ ​​counter-> cond! == 2) ($ counter-> wait (); // počkajte, kým sa druhý spustí ako prvý) pre ($ i = 10; $ i oznámenia (); if ($ counter-> cond === 2) ($ counter-> cond = 1; $ counter-> wait ();))), $ counter); $ counter-> join (); / * Výstup: 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) * /

    Môžete si všimnúť ďalšie podmienky, ktoré boli kladené okolo hovoru na Threaded :: wait. Tieto podmienky sú kritické, pretože umožňujú obnovenie synchronizovaného spätného volania, keď dostane upozornenie a zadaná podmienka je pravdivá. Je to dôležité, pretože upozornenia môžu prichádzať aj z iných miest, ako keď sa volá Threaded :: notification. Ak teda hovory metódy Threaded :: wait neboli uzavreté v podmienkach, urobíme to falošné budíkyčo povedie k nepredvídateľnému správaniu kódu.

    Záver

    Pozreli sme sa na päť tried v balíku pthreads (Thread, Thread, Worker, Volatile a Pool) a na to, ako sa každá z tried používa. Tiež sme sa pozreli na nový koncept nemennosti v pthreads, áno krátka recenzia podporované možnosti synchronizácie. Keď sú tieto základy zavedené, môžeme sa začať zaoberať používaním pthreads v skutočných prípadoch! Toto bude téma nášho ďalšieho príspevku.

    Ak vás zaujíma preklad nasledujúceho príspevku, dajte mi vedieť: komentujte v sociálnych sieťach. siete, plus a zdieľať príspevok s kolegami a priateľmi.



    Nedávno som vyskúšal pthreads a bol som príjemne prekvapený - je to rozšírenie, ktoré pridáva možnosť pracovať s niekoľkými skutočnými vláknami v PHP. Žiadna emulácia, žiadna mágia, žiadne falzifikáty - všetko je skutočné.



    Uvažujem nad takouto úlohou. Existuje množstvo úloh, ktoré je potrebné rýchlo dokončiť. PHP má ďalšie nástroje na riešenie tohto problému, nie sú tu uvedené, článok je o pthreads.



    Čo je to pthreads

    To je všetko! Takmer všetko. V skutočnosti existuje niečo, čo môže zvedavého čitateľa rozrušiť. Nič z toho nefunguje v štandardnom PHP kompilovanom s predvolenými možnosťami. Aby ste si mohli užiť viacvláknové vlákno, musíte mať vo svojom PHP povolený ZTS (Zend Thread Safety).

    Nastavenie PHP

    Ďalej PHP so ZTS. Nevadí veľký rozdiel v behu oproti PHP bez ZTS (37,65 vs 265,05 sekúnd), neskúšal som priniesť nastavenie PHP na spoločného menovateľa. V prípade bez ZTS mám napríklad povolený XDebug.


    Ako vidíte, pri použití 2 vlákien je rýchlosť vykonávania programu asi 1,5 -krát vyššia ako v prípade lineárneho kódu. Pri použití 4 vlákien - 3 krát.


    Môžete venovať pozornosť tomu, že hoci je procesor 8-jadrový, doba vykonania programu sa takmer nezmenila, ak boli použité viac ako 4 vlákna. Zdá sa, že je to kvôli skutočnosti, že fyzické jadrá môjho procesora sú 4. Kvôli prehľadnosti som nakreslil tanier vo forme diagramu.


    Zhrnutie

    PHP zvládne multithreading celkom elegantne pomocou rozšírenia pthreads. To prináša citeľné zvýšenie produktivity.