Програмирање со повеќе нишки во PHP користејќи Pthreads Translation. Пресметување со повеќе нишки во PHP: threads Path of the Jedi - со користење на наставката PCNTL

Неодамна пробав pthreads и бев пријатно изненаден - тоа е екстензија која додава можност за работа со повеќе реални нишки во PHP. Без емулација, без магија, без фалсификати - сè е реално.



Размислувам за таква задача. Има многу задачи што треба брзо да се завршат. PHP има и други алатки за решавање на овој проблем, тие не се споменати овде, статијата е за pthreads.



Што се нишки

Тоа е се! Па, речиси сè. Всушност, има нешто што може да го вознемири љубопитниот читател. Ништо од ова не работи на стандарден PHP компајлиран со стандардни опции. За да уживате во повеќенишки, мора да имате вклучено ZTS (Zend Thread Safety) во вашиот PHP.

Поставување PHP

Следно, PHP со ZTS. Не ми пречи големата разлика во времето на извршување во споредба со PHP без ZTS (37,65 наспроти 265,05 секунди), не се обидов да го генерализирам поставувањето на PHP. Во случајот без ZTS, имам вклучено XDebug на пример.


Како што можете да видите, кога користите 2 нишки, брзината на извршување на програмата е приближно 1,5 пати поголема отколку во случајот со линеарен код. Кога користите 4 нишки - 3 пати.


Можете да забележите дека иако процесорот е 8-јадрен, времето на извршување на програмата остана речиси непроменето ако се користат повеќе од 4 нишки. Се чини дека ова се должи на фактот дека мојот процесор има 4 физички јадра. За јасност, ја прикажав плочата во форма на дијаграм.


Резиме

Во PHP, можно е да се работи доста елегантно со повеќенишки користејќи ја екстензијата pthreads. Ова дава забележливо зголемување на продуктивноста.

Ознаки: Додадете ознаки

Понекогаш станува неопходно да се извршат неколку дејства истовремено, на пример, проверка на промените во една табела со база на податоци и правење модификации на друга. Покрај тоа, ако една од операциите (на пример, проверка на промените) одзема многу време, очигледно е дека секвенцијалното извршување нема да обезбеди балансирање на ресурсите.

За да се реши овој вид проблем, програмирањето користи повеќенишки - секоја операција се става во посебна нишка со доделена количина на ресурси и работи внатре во неа. Со овој пристап, сите задачи ќе се завршат посебно и независно.

Иако PHP не поддржува мултинишки, постојат неколку методи за негово емулирање, кои ќе бидат разгледани подолу.

1. Извршување на неколку копии од сценариото - една копија по операција

//woman.php ако (!isset($_GET["нишка"])) ( систем ("wget ​​· http://localhost/woman.php?thread=make_me_happy"); систем ("wget ​​· http: //localhost/ woman.php?thread=make_me_rich"); ) elseif ($_GET["thread"] == "make_me_happy") ( make_her_happy(); ) elseif ($_GET["thread"] == "make_me_rich" ) ( find_other_one( );)

Кога ја извршуваме оваа скрипта без параметри, таа автоматски извршува две копии од себе, со ИД на операции („thread=make_me_happy“ и „thread=make_me_rich“), кои го иницираат извршувањето на потребните функции.

На овој начин го постигнуваме посакуваниот резултат - две операции се извршуваат истовремено - но ова, се разбира, не е повеќенаменски, туку едноставно патерица за извршување на задачи истовремено.

2. Патека на Џедај - со користење на наставката PCNTL

PCNTL е екстензија која ви овозможува целосно да работите со процесите. Покрај управувањето, поддржува испраќање пораки, проверка на статусот и поставување приоритети. Вака изгледа претходната скрипта со PCNTL:

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

Изгледа прилично збунувачки, ајде да поминеме низ него ред по ред.

Во првата линија, го „раздвојуваме“ тековниот процес (вилушка е копирање на процес додека се зачувуваат вредностите на сите променливи), делејќи го на два процеси (тековен и дете) кои работат паралелно.

За да разбереме дали моментално сме во процес на дете или мајка, функцијата pcntl_fork враќа 0 за детето и ID на процесот за мајката. Затоа, во втората линија гледаме на $pid, ако е нула, тогаш сме во дете процес - ја извршуваме функцијата, инаку, сме во мајката (линија 4), тогаш создаваме друг процес и на сличен начин ја извршуваат задачата.

Процес на извршување на скрипта:

Така, скриптата создава уште 2 детски процеси, кои се негови копии и ги содржат истите променливи со слични вредности. И користејќи го идентификаторот вратен од функцијата pcntl_fork, дознаваме во која нишка сме моментално и ги извршуваме потребните дејства.

Се чини дека програмерите на PHP ретко користат истовремено. Нема да зборувам за едноставноста на синхрониот код; програмирањето со една нишка е, се разбира, поедноставно и појасно, но понекогаш мала употреба на паралелизам може да донесе забележително зголемување на перформансите.

Во оваа статија, ќе погледнеме како може да се постигне повеќенишки во PHP користејќи ја наставката pthreads. Ова ќе бара инсталирана верзија ZTS (Zend Thread Safety) на PHP 7.x, заедно со инсталираната екстензија pthreads v3. (Во моментот на пишување, во PHP 7.1, корисниците ќе треба да инсталираат од главната гранка во складиштето на threads - видете наставка од трета страна.)

Мало појаснување: pthreads v2 е наменет за PHP 5.x и повеќе не е поддржан, pthreads v3 е за PHP 7.x и активно се развива.

После ваква дигресија, да преминеме директно на поентата!

Обработка на еднократни задачи

Понекогаш сакате да обработувате еднократни задачи на повеќенишки начин (на пример, извршување на некоја I/O-врзана задача). Во такви случаи, можете да ја користите класата Thread за да креирате нова нишка и да извршите одредена обработка на посебна нишка.

На пример:

$task = новата класа ја проширува темата ( приватна $response; јавна функција стартува() ( $content = file_get_contents ("http://google.com"); preg_match ("~ (.+)~", $content, $matches); $this->response = $matches; ) ); $task->start() && $task->join(); var_dump($task->response); // стринг (6) „Гугл“

Овде методот на извршување е нашата обработка, која ќе се изврши во нова нишка. Кога се повикува Thread::start, се отвора нова нишка и се повикува методот на извршување. Потоа ја спојуваме детската нишка назад во главната нишка со повикување Thread::join , што ќе блокира додека детската нишка не заврши со извршување. Ова осигурува дека задачата ќе заврши со извршувањето пред да се обидеме да го испечатиме резултатот (кој е зачуван во $task->response).

Можеби не е пожелно да се загади класа со дополнителни одговорности поврзани со логиката на протокот (вклучувајќи ја одговорноста за дефинирање на методот на работа). Можеме да разликуваме такви класи со нивно наследување од класата Threaded. Потоа тие можат да се вметнат во друга нишка:

Задачата на класата се проширува со нишки ( јавна $response; јавна функција someWork() ( $content = file_get_contents ("http://google.com"); preg_match ("~ (.+) ~", $content, $matches); $ this->response = $ matches; ) ) $task = нова задача; $thread = нова класа($task) ја проширува Нимата ( приватна $task; јавна функција __construct(Treaded $task) ( $this->task = $task; ) јавна функција run() ( $this->task->someWork( );)); $thread->start() && $thread->join(); var_dump($task->response);

Секоја класа што треба да се изврши во посебна нишка моранаследуваат од класата Threaded. Тоа е затоа што ги обезбедува потребните способности за извршување на обработка на различни нишки, како и имплицитна безбедност и корисни интерфејси (како што е синхронизација на ресурси).

Ајде да ја погледнеме хиерархијата на класите што ја нуди екстензијата pthreads:

Thread Worker Испарливиот базен со навој (имплементира Преминлив, собирлив).

Веќе ги опфативме и научивме основите на часовите Thread и Threaded, сега да ги погледнеме другите три (Worker, Volatile и Pool).

Повторна употреба на теми

Започнувањето нова нишка за секоја задача што треба да се паралелизира е прилично скапо. Тоа е затоа што архитектурата за заедничко-ништо мора да се имплементира во threads за да се постигне мултинишка во рамките на PHP. Што значи дека целиот контекст на извршување на тековната инстанца на PHP толкувачот (вклучувајќи ја секоја класа, интерфејс, особина и функција) мора да се копира за секоја креирана нишка. Бидејќи ова има забележливо влијание врз перформансите, преносот секогаш треба повторно да се користи секогаш кога е можно. Нишките може повторно да се користат на два начина: со користење на Workers или со користење на Pools.

Класата Worker се користи за извршување на одреден број задачи синхроно во друга нишка. Ова се прави со креирање на нов Worker пример (кој создава нова нишка), а потоа ги турка задачите во стекот на таа посебна нишка (користејќи Worker::stack).

Еве мал пример:

Класата задача се проширува Threaded ( приватна $value; јавна функција __construct(int $i) ( $this->value = $i; ) јавна функција run() ( usleep(250000); echo "Task: ($this->value) \n"; ) ) $worker = нов Работник(); $worker->start(); за ($i = 0; $i стек(нова задача($i)); ) додека ($worker->collect()); $worker-> shutdown();

Во примерот погоре, 15 задачи за нов објект $worker се туркаат на оџакот преку методот Worker::stack, а потоа се обработуваат по редоследот по кој биле туркани. Методот Worker::collect, како што е прикажано погоре, се користи за чистење на задачите веднаш штом ќе завршат со извршувањето. Со него, внатре во јамката while, ја блокираме главната нишка додека не се завршат и исчистат сите задачи на стекот - пред да го повикаме Worker::shutdown . Предвремено прекинување на работникот (т.е. додека има уште задачи што треба да се завршат) сепак ќе ја блокира главната нишка додека сите задачи не го завршат нивното извршување, само за задачите да не се собираат ѓубре (што подразбира протекување на меморијата).

Класата Worker обезбедува неколку други методи поврзани со нејзиниот стек задачи, вклучувајќи Worker::unstack за отстранување на последната наредена задача и Worker::getStacked за добивање на бројот на задачи во оџакот за извршување. Стакот на работникот ги содржи само задачите што треба да се извршат. Откако ќе се заврши задачата од стекот, таа се отстранува и се става на посебен (внатрешен) стек за собирање ѓубре (со користење на методот Worker::collect).

Друг начин за повторна употреба на нишка во повеќе задачи е да се користи нишки базен (преку класата Pool). Базенот со нишки користи група работници за да овозможи извршување на задачите истовремено, во кој факторот на истовременост (бројот на базен нишки со кои работи) е поставен кога базенот се креира.

Ајде да го прилагодиме горенаведениот пример за да користиме група работници:

Класата задача се проширува Threaded ( приватна $value; јавна функција __construct(int $i) ( $this->value = $i; ) јавна функција run() ( usleep(250000); echo "Task: ($this->value) \n";) ) $pool = нов Базен(4); за ($i = 0; $i поднесе (нова задача($i)); ) додека ($pool->collect()); $pool->shutdown();

Има неколку забележителни разлики кога се користи базен за разлика од работник. Прво, базенот не треба рачно да се стартува; тој започнува да ги извршува задачите веднаш штом ќе станат достапни. Второ, ние испратизадачи до базенот, не ставете ги на оџак. Дополнително, класата Pool не наследува од Threaded и затоа не може да се пренесе на други нишки (за разлика од Worker).

Добра практика е работниците и базените секогаш да ги чистат своите задачи веднаш штом ќе ги завршат, а потоа самите да ги прекинат рачно. Нишките создадени со помош на класата Thread мора да се прикачат и на матичната нишка.

нишки и (не)променливост

Последната класа што ќе ја допреме е Volatile, нов додаток на pthreads v3. Непроменливоста стана важен концепт во темите бидејќи без неа перформансите значително страдаат. Затоа, стандардно, својствата на Threaded класите кои самите се Threaded објекти сега се непроменливи и затоа не можат да се презапишат по нивната првична задача. Во моментов се претпочита експлицитна променливост за таквите својства, и сè уште може да се постигне со користење на новата класа Испарливи.

Ајде да погледнеме пример кој ќе ги покаже новите ограничувања за непроменливост:

Задачата на класата ја проширува Threaded // класата Threaded ( јавна функција __construct() ( $this->data = new Threaded(); // $this->податоците не се препишуваат, бидејќи се својство на Threaded на класата Threaded ) ) $task = нова класа(new Task()) ја проширува Thread ( // a Threaded class, бидејќи Thread ја проширува Threaded public функција __construct($tm) ( $this->threadedMember = $tm; var_dump($this->threadedMember-> податоци) // објект (Threaded)#3 (0) () $this->threadedMember = new StdClass();// неважечки, бидејќи својството е Threaded член на Threaded класа ) );

Својствата со навој на испарливите класи, од друга страна, се променливи:

Задачата на класата го проширува Volatile ( јавна функција __construct() ( $this->data = new Threaded(); $this->data = new StdClass(); // важи, бидејќи сме во испарлива класа ) ) $task = ново class(new Task()) ја проширува нишката (јавна функција __construct($vm) ($this->volatileMember = $vm; var_dump($this->volatileMember->data); // објект(stdClass)#4 (0) () // сè уште е невалиден, бидејќи Volatile го проширува Threaded, така што имотот е сè уште член на Threaded на класата Threaded $this->volatileMember = new StdClass(); ) );

Можеме да видиме дека класата Volatile ја надминува непроменливоста наметната од матичната класа Threaded за да обезбеди можност за промена на својствата Threaded (како и unset()).

Постои уште еден предмет на дискусија за опфат на темата за варијабилност и класата Volatile - низи. Во threads, низите автоматски се префрлаат на испарливи објекти кога се доделуваат на својство од класата Threaded. Ова е затоа што едноставно не е безбедно да се манипулира со низа од повеќе PHP контексти.

Ајде повторно да погледнеме пример за подобро да разбереме некои работи:

$ низа = ; $task = нова класа ($array) ја проширува Низата ( приватна $data; јавна функција __construct (низа $array) ( $this->data = $array; ) јавна функција run() ( $this->data = 4; $ this->податоци = 5; print_r($this->data); ) ); $task->start() && $task->join(); /* Излез: испарлив објект ( => 1 => 2 => 3 => 4 => 5) */

Гледаме дека испарливите објекти може да се третираат како да се низи бидејќи поддржуваат операции со низи како што е (како што е прикажано погоре) операторот подмножество(). Сепак, испарливите класи не поддржуваат основни функции на низа како што се array_pop и array_shift. Наместо тоа, класата Threaded ни обезбедува такви операции како вградени методи.

Како демонстрација:

$data = новата класа се проширува Нестабилно (јавно $a = 1; јавно $b = 2; јавно $c = 3; ); var_dump ($податоци); var_dump ($data->pop()); var_dump ($data->shift()); var_dump ($податоци); /* Излез: објект(класа@анонимно)#1 (3) ( ["a"]=> int(1) ["b"]=> int(2) ["c"]=> int(3) ) int(3) int(1) објект(class@anonymous)#1 (1) ( ["b"]=> int(2) ) */

Други поддржани операции вклучуваат Threaded::chunk и Threaded::merge .

Синхронизација

Во последниот дел од овој напис, ќе ја разгледаме синхронизацијата во pthreads. Синхронизацијата е метод кој ви овозможува да го контролирате пристапот до споделените ресурси.

На пример, да спроведеме едноставен бројач:

$counter = новата класа ја проширува Thread ( јавна $i = 0; јавна функција run() ( за ($i = 0; $i i; ) ) ); $counter->start(); за ($i = 0; $i i; ) $counter->join(); var_dump ($counter->i); // ќе отпечати број од 10 до 20

Без употреба на синхронизација, излезот не е детерминистички. Повеќе нишки запишуваат на истата променлива без контролиран пристап, што значи дека ажурирањата ќе бидат изгубени.

Ајде да го поправиме ова за да го добиеме точниот излез од 20 со додавање на времето:

$counter = новата класа ја проширува Thread ( јавна $i = 0; јавна функција run() ( $this->synchronized(function () ( for ($i = 0; $i i; ) )); ) ); $counter->start(); $counter->synchronized(функција ($counter) ( за ($i = 0; $i i; ) ), $counter); $counter->join(); var_dump ($counter->i); // int(20)

Синхронизираните кодни блокови исто така можат да комуницираат едни со други користејќи ги методите Threaded::wait и Threaded::notify (или Threaded::notifyAll).

Еве алтернативно зголемување во две синхронизирани додека јамки:

$counter = новата класа ја проширува Низата ( јавна $cond = 1; јавна функција run() ( $this->synchronized(function () ( за ($i = 0; $i notify(); ако ($this->cond === 1) ( $this->cond = 2; $this->wait(); ) ))); ) ); $counter->start(); $counter->synchronized(функција ($counter) ( if ($counter->cond !== 2) ( $counter->wait(); // почекајте другата да почне прва ) за ($i = 10; $i notify();if ($counter->cond === 2) ( $counter->cond = 1; $counter->wait(); ) ) ), $counter); $counter->join(); /* Излез: 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) */

Може да забележите дополнителни услови кои се поставени околу повикот до Threaded::wait . Овие услови се критични бидејќи дозволуваат синхронизираниот повратен повик да продолжи кога ќе прими известување и кога наведената состојба е вистинита. Ова е важно бидејќи известувањата може да доаѓаат од други места освен кога се повикува Threaded::notify. Така, ако повиците до методот Threaded::wait не беа затворени во услови, ќе извршиме лажни повици за будење, што ќе доведе до непредвидливо однесување на кодот.

Заклучок

Ги разгледавме петте класи на пакетот threads (Threaded, Thread, Worker, Volatile и Pool) и како се користи секоја класа. Го разгледавме и новиот концепт на непроменливост на нишките и дадовме краток преглед на поддржаните способности за синхронизација. Со воспоставување на овие основи, сега можеме да почнеме да гледаме како threads може да се користат во случаи во реалниот свет! Ова ќе биде тема на нашиот следен пост.

Доколку ве интересира преводот на следната објава, дозволете ми да знам: коментирајте на социјалните мрежи. мрежи, гласајте позитивно и споделете ја објавата со колегите и пријателите.

  • Програмирање,
  • Паралелно програмирање
  • Неодамна пробав pthreads и бев пријатно изненаден - тоа е екстензија која додава можност за работа со повеќе реални нишки во PHP. Без емулација, без магија, без фалсификати - сè е реално.



    Размислувам за таква задача. Има многу задачи што треба брзо да се завршат. PHP има и други алатки за решавање на овој проблем, тие не се споменати овде, статијата е за pthreads.



    Што се нишки

    Тоа е се! Па, речиси сè. Всушност, има нешто што може да го вознемири љубопитниот читател. Ништо од ова не работи на стандарден PHP компајлиран со стандардни опции. За да уживате во повеќенишки, мора да имате вклучено ZTS (Zend Thread Safety) во вашиот PHP.

    Поставување PHP

    Следно, PHP со ZTS. Не ми пречи големата разлика во времето на извршување во споредба со PHP без ZTS (37,65 наспроти 265,05 секунди), не се обидов да го генерализирам поставувањето на PHP. Во случајот без ZTS, имам вклучено XDebug на пример.


    Како што можете да видите, кога користите 2 нишки, брзината на извршување на програмата е приближно 1,5 пати поголема отколку во случајот со линеарен код. Кога користите 4 нишки - 3 пати.


    Можете да забележите дека иако процесорот е 8-јадрен, времето на извршување на програмата остана речиси непроменето ако се користат повеќе од 4 нишки. Се чини дека ова се должи на фактот дека мојот процесор има 4 физички јадра. За јасност, ја прикажав плочата во форма на дијаграм.


    Резиме

    Во PHP, можно е да се работи доста елегантно со повеќенишки користејќи ја екстензијата pthreads. Ова дава забележливо зголемување на продуктивноста.

    Тагови:

    • php
    • нишки
    Додадете ознаки

    Неодамна пробав pthreads и бев пријатно изненаден - тоа е екстензија која додава можност за работа со повеќе реални нишки во PHP. Без емулација, без магија, без фалсификати - сè е реално.



    Размислувам за таква задача. Има многу задачи што треба брзо да се завршат. PHP има и други алатки за решавање на овој проблем, тие не се споменати овде, статијата е за pthreads.



    Што се нишки

    Тоа е се! Па, речиси сè. Всушност, има нешто што може да го вознемири љубопитниот читател. Ништо од ова не работи на стандарден PHP компајлиран со стандардни опции. За да уживате во повеќенишки, мора да имате вклучено ZTS (Zend Thread Safety) во вашиот PHP.

    Поставување PHP

    Следно, PHP со ZTS. Не ми пречи големата разлика во времето на извршување во споредба со PHP без ZTS (37,65 наспроти 265,05 секунди), не се обидов да го генерализирам поставувањето на PHP. Во случајот без ZTS, имам вклучено XDebug на пример.


    Како што можете да видите, кога користите 2 нишки, брзината на извршување на програмата е приближно 1,5 пати поголема отколку во случајот со линеарен код. Кога користите 4 нишки - 3 пати.


    Можете да забележите дека иако процесорот е 8-јадрен, времето на извршување на програмата остана речиси непроменето ако се користат повеќе од 4 нишки. Се чини дека ова се должи на фактот дека мојот процесор има 4 физички јадра. За јасност, ја прикажав плочата во форма на дијаграм.


    Резиме

    Во PHP, можно е да се работи доста елегантно со повеќенишки користејќи ја екстензијата pthreads. Ова дава забележливо зголемување на продуктивноста.



    
    Врв