Futures
Školení, která pořádám
Jednou v životě jsem vytvářel vícevláknovou aplikaci a přestože jsem to bez větších potíží zvládl, tak jsem rád, že to není mým denním chlebem. V PHP vlákna obvykle nejsou potřeba, protože zdroje serveru bohatě vytíží požadavky, které paralelně zpracovává webový server. Pokud bychom se rozhodli ve více vláknech provádět nějaké výpočty, tak nic neušetříme, protože ostatní jádra stejně vyřizují další požadavky, takže bychom akorát víc zaplatili za context switching.
Jsou však situace, kdy se hodí dělat víc věcí najednou, obzvlášť když na něco čekáme – na vyřízení HTTP požadavku, na doběhnutí programu příkazového řádku nebo na přenesení dat z databáze. Vlákna ale naštěstí nejsou jediný způsob, jak toho docílit. Já jsem si velmi oblíbil koncept futures, který používáme ve Phabricatoru. Práce s nimi je totiž velmi jednoduchá a zajišťují přesně to, co je nejvíc potřeba – tedy čekání na víc věcí najednou.
Jak se s futures pracuje, ukáži na jednoduchém příkladu: Pro každý podadresář v aktuálním adresáři chceme spustit git log -n 1 a s jeho výsledkem něco udělat. Při použití futures je kód snadno pochopitelný:
<?php
// příprava futures
$futures = array();
foreach (glob("*", GLOB_ONLYDIR) as $dir) {
$futures[$dir] = new ExecFuture("git log -n 1 %s", $dir);
}
// spuštění futures
foreach (Futures($futures) as $dir => $future) {
list($stdout, $stderr) = $future->resolvex();
// tady něco uděláme se $stdout
}
?>
Vlastně jsme jen oddělili přípravu příkazů a jejich spuštění. Pokud by příprava byla složitější, tak to čitelnosti dokonce může prospět.
Iterátor Futures vrací úlohy v pořadí, jak se postupně vyhodnocují, což samozřejmě nemusí být pořadí, ve kterém byly uloženy v poli. To se hodí hlavně v situaci, kdy na základě výsledku chceme zahájit další práci nebo když výsledky průběžně zobrazujeme.
Možnosti této implementace jsou bohatší, např. metodou limit můžeme omezit počet paralelně prováděných úloh nebo metodou addFuture můžeme do iterátoru přidat další úlohu. Ve většině případů však stačí to nejzákladnější API.
Knihovna libphutil nabízí tři základní futures: ExecFuture, BaseHTTPFuture a QueryFuture.
Diskuse
Hlavní síla futures spocívá v možnosti jejich kompozice do neblokujících flow. Stačí k tomu jediný kombinátor `then` (coř je jenom převlečené nebo map/flatMap) a z něj se dají postavit všechny potřebné utility pro koordinaci (viz. React.PHP https://github.com/reactphp/promise ).
<?php
$futureAgain = (new ExecFuture("git log -n 1 %s", $dir))->then($doSomething)->then($doSomethingElse);
?>
I když je mi jasné, že v tomhle případě nebude nějaká rozsáhlá koordinace třeba.
Moje zkušenost s then() je taková, že kód se stává celkem rychle mnohem míň přehledný. V případech, kdy to je potřeba, preferuji spíš použití yield.
Honza Tvrdík:
Jenom doplním, že pěkný příklad, jak nahradit volaní $promise->then(...) pomocí yieldu (od PHP 5.5) měl včera kaja47 v přednášce o React.PHP na Poslední sobotě. Konkrétně ta ukázka je na slidech 54 až 61
https://speakerdeck.com/kaja47/react-dot-php?slide=54.
Jirka:
Používá to někdo v produkci? Dokumentace o libphutil říká "unstable preview release", což nezní moc přesvědčivě.
Navíc nemám rád maštění všeho do jednoho, dávám přednost malým jednoúčelovým jednoduchým stavebním prvkům, takže pro dosažení stejného výsledku mi přijde vhodnější kombinace malých procesů, propojených s hlavním kódem pomocí messaging serveru, např. Redis.
Ano, v produkci to používá např. Facebook nebo Dropbox. "unstable preview release" smažeme.
Rozhodně bych tento přístup nenazýval "maštění všeho do jednoho". Třeba v této ukázce potřebuji spustit nějaký příkaz a jeho výsledky zpracovat. Přijde mi mnohem lepší, když to vidím v kódu vedle sebe – jsou to logicky související věci a ve vyčlenění někam stranou nevidím žádný přínos.
sh:
Já poprvé narazil na koncept Future v Dartu, kde je to implementovano v core knihovnach pro asynchronicitu. (Pak jeste Streamy futures, ale to je jina pohadka, moc se mi kvuli tomu vlastne ani nechce vracet k php)
Kazdopadne je to pro me (synchronniho php programatora) novej svet s velkym mnozstvim jak vyhod tak "zadrhelu" na ktere clovek prichazi teprv kdyz s tim programuje. Nevis jestli existuje nejaka bible toho jak to vlastne funguje a jak spravne asynchronne programovat? (at uz pro jakejkoliv jazyk...)
Diskuse je zrušena z důvodu spamu.