Internet Info, s.r.o. Lupa Měšec Podnikatel Root Zdroják DigiZone Slunečnice Vitalia TopDrive KupDnes Navrcholu NovýTarif Dobrý web Weblogy Woko Jagg Computer.cz SK: MojeLinky

Hlavní navigace

PHP okénko: Paralelní zpracování

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.

Tweetni to Twitter Jaggni to! Jagg Del.icio.us Delicious

Ř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:

davame_internetu_obsah
       
<?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.

Jakub Vrána

Jakub Vrána

Autor se živí programováním v PHP, podílí se na jeho oficiální dokumentaci, vyučuje ho na MFF UK a vede odborná školení. Poznámky si zapisuje na weblog PHP triky.

Školení: Linux – Firewall, Samba, VPN

Na třídenním školení se naučíte nainstalovat a spravovat Firewall a Router, SAMBA Doménový a Souborový server. Dále si zprovozníte vlastní, zabezpečený VPN server.

Podrobnější informace a přihláška

Ohodnoťte jako ve škole:
Průměrná známka 2,86

Přehled názorů

CygPHP
Michal Molhanec 23. 5. 2005 00:07
Nový
└ 
Re: CygPHP
Jakub Vrána 18. 7. 2005 10:35
Nový
Chybi verze
anonymní uživatel 25. 5. 2005 16:26
Nový
└ 
Re: Chybi verze
Jakub Vrána 25. 5. 2005 16:33
Nový
Zajimavy clanek
Honza 25. 5. 2005 23:34
Nový
       

Tento text je již více než dva měsíce starý. Chcete-li na něj reagovat v diskusi, pravděpodobně vám již nikdo neodpoví. Pro řešení aktuálních problémů doporučujeme využít naše diskusní fórum.

Zasílat nově přidané příspěvky e-mailem