PHP okénko: Paralelní zpracování

Jakub Vrána 23. 5. 2005

Dnešní PHP okénko představuje na jednoduchém příkladu možnosti PHP při paralelním načítání dat a ukazuje jeden nový obrat v PHP 5.

Řekněme, že bychom chtěli vytvořit kód zjišťující, kolik jednotlivé vyhledávače vrací výsledků při hledání různých slov. Když pominu omáčku okolo, mohl by kód vypadat nějak takhle:

<?php
$vyhledavace = array( // nazev => array(url, pattern), ...
    'Seznam.cz' => array('http://search.seznam.cz/search.cgi?w=', '<b>([ 0-9]+)</b> nalezen'),
    'Centrum.cz' => array('http://search.centrum.cz/index.php?q=', 'Nalezeno <strong>([ 0-9]+)</strong>'),
    'Atlas.cz' => array('http://search.atlas.cz/default.aspx?q=', '<b>([ 0-9]+)</b></span> nalezen'),
    'Jyxo.cz' => array('http://jyxo.cz/s?s=', 'Jyxo nalezlo <b>([0-9]+)</b>'),
    'Google.com' => array('http://www.google.com/search?q=', 'of about <b>([,0-9]+)</b>'),
);
foreach ($vyhledavace as $nazev => $vyhledavac) {
    $file = file_get_contents($vyhledavac[0] . urlencode($_GET["slovo"]));
    echo "$nazev: " . (preg_match("~$vyhledavac[1]~", $file, $matches) ? $matches[1] : "nenalezeno") . "\n";
}
?> 

K dokonalosti kódu ještě leccos chybí (různé vyhledávače např. používají různé kódování a je jim potřeba posílat některé hlavičky), ale o tom teď psát nechci. Při spuštění si nejde nevšimnout toho, že skript většinu času jenom čeká na stažení dat, a je proto zbytečně pomalý. Rozdělením toku do vláken PHP nedisponuje, ale díky rozšíření PCNTL je možné tok kódu rozdělit do nezávislých procesů. Princip je stejný jako v jazyce C – funkce pcntl_fork vytvoří nový proces, rodičovi vrátí číslo procesu potomka a potomkovi nulu. Kód tedy upravíme tak, že rodič vytvoří pro každý vyhledávač samostatný proces a dotaz do vyhledávače bude klást tento potomek. Tím dosáhneme paralelního zpracování.

<?php
foreach ($vyhledavace as $nazev => $vyhledavac) {
    $pid = pcntl_fork();
    if (!$pid || $pid == -1) { // potomek nebo nelze forknout
        $file = file_get_contents($vyhledavac[0] . urlencode($_GET["slovo"]));
        echo "$nazev: " . (preg_match("~$vyhledavac[1]~", $file, $matches) ? $matches[1] : "nenalezeno") . "\n";
        if (!$pid) { // pro potomka práce skončila
            exit();
        }
    }
}

while (pcntl_wait($status) > 0) {
    // počkáme na dokončení všech dětí, ať se z nich nestanou zombie
}
?> 

Výsledky z jednotlivých vyhledávačů se budou vypisovat v tom pořadí, v jakém budou získány. Pokud bychom místo toho chtěli data z dětí předávat rodičovi, hodila by se funkce msg_send, ale o tom zase až někdy příště.

Rozšíření PCNTL je za normální okolností k dispozici pouze pro systémy typu Unix, na Windows můžete použít CygPHP – speciální kompilaci, která obsahuje mimo jiné i toto rozšíření. Rozšíření je navíc k dispozici pouze v prostředích CGI a CLI, takže se dá použít hlavně při spouštění skriptů z příkazové řádky – při použití na webu běží PHP obvykle jako modul webového serveru.

Využití knihovny CURL

Řešení s přidáváním procesů je univerzálně použitelné, ale poměrně nehospodárné. Pokud nám jde pouze o paralelní načítání dat ze sítě, existují i jiné prostředky. Alternativní způsob řešení podobného problému s využitím asynchronního připojení popisuje na svém blogu Wez Furlong. Jeho řešení je hodně nízkoúrovňové, já proto ukážu, jak se tento problém dá řešit s využitím rozšíření CURL a funkcí curl_multi_*, které byly přidány v PHP 5:

<?php
$curl_multi = curl_multi_init();
foreach ($vyhledavace as &$val) {
    $val[2] = curl_init($val[0] . urlencode($_GET["slovo"]));
    curl_setopt($val[2], CURLOPT_RETURNTRANSFER, true);
    curl_multi_add_handle($curl_multi, $val[2]);
}
unset($val);
?> 

V první části se s využitím funkce curl_multi_ad­d_handle inicializuje multi stack. Cyklus foreach využívá novou vlastnost PHP 5 – proměnná $val je předávaná referencí, takže změny v ní se projeví přímo v poli. Po skončení cyklu je vhodné tuto proměnnou zrušit, protože jinak se do ní přiřazené hodnoty promítnou i do posledního prvku pole. Identifikátor připojení ještě budeme potřebovat, proto se ukládá jako třetí prvek jednotlivých polí v proměnné  $vyhledavace.

<?php
do {
    curl_multi_select($curl_multi);
    while (CURLM_CALL_MULTI_PERFORM == curl_multi_exec($curl_multi, $running)) {
        // provést znovu
    }
    if ($running && strtoupper(substr(PHP_OS, 0, 3)) == "WIN") {
        sleep(1); // pod Windows curl_multi_select() nečeká
    }
} while ($running);
?> 

V druhé části se funkcí curl_multi_select čeká na dostupnost dat a funkcí curl_multi_exec se tato data přijímají. Na Windows funkce curl_multi_select bohužel skončí okamžitě, takže čekání zajistíme alespoň funkcí sleep.

Po stažení všech dat se již jen vypíšou výsledky:

<?php
foreach ($vyhledavace as $nazev => $val) {
    $file = curl_multi_getcontent($val[2]);
    echo "$nazev: " . (preg_match("~$val[1]~", $file, $matches) ? $matches[1] : "nenalezeno") . "\n";
}
?> 

Problém paralelního zpracování není v PHP potřeba řešit příliš často – některé úlohy stačí rozdělit na více menších skriptů a jejich paralelní zpracování zajistí přímo webový server. Když na to ale přijde, jsou k dispozici přinejmenším tři možné způsoby. Ideální samozřejmě je, když úloha při čekání na data provádí nějakou výpočetně náročnější operaci – např. kontroluje pravopis nebo hledá syntaktické chyby v dosud stažených souborech.


Podobně laděné texty můžete najít i na autorově weblogu PHP triky.

Našli jste v článku chybu?
Měšec.cz: Udali ho na nelegální software a přišla Policie

Udali ho na nelegální software a přišla Policie

120na80.cz: Jaké plavecké pomůcky vaše dítě ochrání?

Jaké plavecké pomůcky vaše dítě ochrání?

Root.cz: Bitcoin začal vyplácet jen půlku odměn

Bitcoin začal vyplácet jen půlku odměn

Vitalia.cz: Jak může být v uzenině 150 % masa?

Jak může být v uzenině 150 % masa?

DigiZone.cz: ČT: Díky DVB-T2 budou další HD

ČT: Díky DVB-T2 budou další HD

Vitalia.cz: Zmrzlinu? Ani snad ne

Zmrzlinu? Ani snad ne

Podnikatel.cz: Polská vejce na českém pultu Albertu

Polská vejce na českém pultu Albertu

DigiZone.cz: Ve Varech představeni i noví "Četníci"

Ve Varech představeni i noví "Četníci"

DigiZone.cz: Skylink o půlnoci vypnul 12 525

Skylink o půlnoci vypnul 12 525

Lupa.cz: Největší pitominy s logem “nyní smart a připojené”

Největší pitominy s logem “nyní smart a připojené”

Měšec.cz: Platíme NFC mobilem. Konečně to funguje!

Platíme NFC mobilem. Konečně to funguje!

Podnikatel.cz: Italské těstoviny nebyly k mání, tak je začal vyrábět

Italské těstoviny nebyly k mání, tak je začal vyrábět

Lupa.cz: eIDAS: Nepřehnali jsme to s výjimkami?

eIDAS: Nepřehnali jsme to s výjimkami?

DigiZone.cz: Sázka na e-sporty stanici Prima vychází

Sázka na e-sporty stanici Prima vychází

Měšec.cz: Nový sazebník mBank radost nedělá

Nový sazebník mBank radost nedělá

Vitalia.cz: Tohle je Břicháč Tom, co zhubnul 27 kg

Tohle je Břicháč Tom, co zhubnul 27 kg

Vitalia.cz: Taky je nosíte? Barefoot není pro každého

Taky je nosíte? Barefoot není pro každého

120na80.cz: I tuto vodu můžete pít

I tuto vodu můžete pít

Vitalia.cz: Největší chyby při podávání vína?

Největší chyby při podávání vína?

Lupa.cz: Největší torrentový web KickassTorrents padl

Největší torrentový web KickassTorrents padl