Webdesignérův průvodce po HTML5: Multithreading s WebWorkers

Jednou z největších nevýhod JavaScriptu je, že současné implementace provádí skripty v jednom vlákně. Pokud někde navrhnete příliš složitý výpočet (nebo uděláte chybu, která vyústí v zacyklení), přestane web reagovat a nezpracovává události vyvolané uživatelem. Řešit by to měl koncept Web Workers.
Seriál: Webdesignérův průvodce po HTML5 (21 dílů)
- Webdesignérův průvodce po HTML5 – díl nultý 25. 5. 2010
- Webdesignérův průvodce po HTML5 – nová sémantika 1. 6. 2010
- Webdesignérův průvodce po HTML5 – nová sémantika II 8. 6. 2010
- Webdesignérův průvodce po HTML5 – pohyblivé obrázky 15. 6. 2010
- Webdesignérův průvodce po HTML5 – používáme pohyblivé obrázky 22. 6. 2010
- Webdesignérův průvodce po HTML5 – taháme data od návštěvníka 29. 6. 2010
- HTML5 Audio: rádio ve vašich stránkách 13. 7. 2010
- Webdesignérův průvodce po HTML5: Microdata 20. 7. 2010
- AppCache: webové aplikace i bez připojení 27. 7. 2010
- Webdesignérův průvodce po HTML5: WebStorage 3. 8. 2010
- Webdesignérův průvodce po HTML5: Multithreading s WebWorkers 10. 8. 2010
- Webdesignérův průvodce po HTML5: Databáze v prohlížečích 17. 8. 2010
- Webdesignérův průvodce po HTML5: Shrnutí a rozhrnutí 24. 8. 2010
- HTML5: ukládáme si data k elementům 6. 12. 2010
- Webdesignérův průvodce po HTML5: Táhni a srůstej 5. 1. 2011
- HTML5: První krůčky s FileSystem API 15. 2. 2011
- Mobilizujeme web v HTML5 4. 4. 2011
- Single Page Apps a řešení problémů s historií 1. 6. 2011
- Page Visibility API: Kouká na mě vůbec někdo? 10. 8. 2011
- Práce se soubory v prohlížeči, díl 1 15. 8. 2011
- Práce se soubory v prohlížeči, díl 2 5. 9. 2011
Nálepky:
Představte si, že stavíte dům (ti, co mají zkušenosti se stavbou domu v ČR, si pro jistotu představí, že staví někde, kde řemeslníci fungují). Stavíte ho sami – jeden týden děláte vodu, další elektroinstalaci, další zavádíte plyn… Když děláte jednu věc, nemůžete dělat jinou. A teď si představte, že máte k dispozici řemeslníky, kteří přijdou, udělají to co udělat mají, uklidí po sobě a zase odejdou. Na stavbu si jich klidně najmete víc, takže zatímco v jedné místnosti elektrikář tahá dráty a zapojuje lustr, tak v jiné už se malíř ohání štětkou a v další jiná parta pokládá koberec. Nakonec je dům postavený rychleji a kvalitněji, než kdyby všechny činnosti dělal jeden jediný člověk jednu po druhé.
Připadá vám to jako velká fantazie? No, pravděpodobně ano – většina z nás nemá příliš dobré zkušenosti s řemeslníky. Naštěstí pro webdesignéry není „dobrý řemeslník“ zas taková fantazie – pokud máme na mysli ty digitální. Seznamte se s webovými řemeslníky – s technologií Web Workers.
K čemu jsou Web Workers
Už jste někdy byli na stránce, která se sice zobrazila, ale nereagovala na klikání? Nebo na stránce, která úspěšně zasekla váš prohlížeč a řekla si o 98 % dostupného výkonu CPU? Ve většině případů je příčinou JavaScript. Webové stránky používají JavaScript čím dál víc, webdesignéři jej mají rádi, protože jim umožňuje vytvářet velmi interaktivní weby, ale daní za to, že fungují všude možně, je i to, že leckdy běží na strojích, které nemají výkon odpovídající nárokům dnešních webových aplikací. Existují postupy jak optimalizovat JavaSript, ale rychlosti nativního kódu se nepřiblížíme ani zdaleka. Navíc některé problémy ani sebelepší optimalizací nevyřešíme.
Webdesignéři se ale kvůli tomu nemusí vzdávat využití JavaScriptu. Místo toho jim vycházejí vstříc tvůrci prohlížečů a nabízejí technologie, které zlepšují kvalitu práce s JavaScriptem. Příkladem takové technologie jsou právě Web Workers.
Jak Web Workers fungují
Většina moderních jazyků (a moderních systémů) je vícevláknová (multi-threaded), což znamená, že podporují vykonávání několika procesů zároveň. Udělat JavaScript vícevláknový by znamenalo udělat jej znovu (a jinak), proto přichází Web Workers s řešením, které za jistých okolností umožní spuštění více vláken najednou. S určitými omezeními lze pak provádět víc než jeden proces v jednu chvíli.
Kde to lze použít?
Vraťme se k naší analogii s řemeslníky na stavbě: Web Workers jsou takoví jednoúčeloví pomocníci (přidavači), kteří dělají jen jednu věc, ale dělají ji dobře. Jsou perfektní na rychlé výpočty, ale složitější práce, jako třeba operace s DOMem (neplést s „domem“), dělat nemohou. Kdybychom náš web přirovnali ke kuchyni, tak si můžeme hlavní JavaScript představit jako Vedoucího Výroby Omelet. Když dělá všechno sám, musí připravit pánev, rozehřát tuk, rozbít vejce a smažit. Pokud chce být produktivní, může si najmout pomocníka, který bude rozbíjet vajíčka místo něj, což mu umožní, aby se věnoval přípravě pánve a vlastnímu smažení. Pomocník nesmí na pánev sahat, pouze plní zadaný úkol, aby se šéfkuchař mohl věnovat jiné práci.

Jak může pomocník usnadnit výrobu omelet
S Web Workers je to podobné. Pokud potřebujete v JavaScriptu zařídit nějaké výpočetně náročné operace, můžete je přenechat nějakému pomocníkovi, aby se postaral o potřebné, zatímco hlavní proces nerušeně pokračuje dál. Můžete si takových pomocníků vytvořit několik, a každý může dělat víc činností. Pojďme si je ukázat na příkladu.
Umí váš prohlížeč Web Workers? Zjistíte to rychle a přehledně v našem detektoru podpory technologií z rodiny HTML5. Naleznete v něm i odkazy na další články, které se zabývají tímto tématem.
Prostě ukaž zdrojový kód!
Ještě vydržte, už tam skoro budeme! Worker sám je prostě jen kus JavaScriptu ve vlastním souboru. Protože základem konceptu Web Workers je vykonávání kódu v oddělených vláknech, je třeba, aby byly tyto odděleně vykonávané kódy v samostatných souborech. Co pomocník – Web Worker, to vlastní soubor. V našem příkladu začneme tím, že si vytvoříme prázdný soubor a nazveme jej worker.js
.
V hlavním JavaScriptovém vlákně použijeme odkaz na tento soubor při vytváření nového pomocníka – objektu Worker
:
var worker = new Worker('worker.js');
Pomocníkovi dáme něco ke zpracování, on s tím něco udělá a vrátí nám výsledek své práce – podobně jako ten výše zmíněný kuchtík. S Workerem se domlouváme pomocí metody postMessage
:
// Vytvoříme nový objekt worker var worker = new Worker('worker.js'); // Pošleme mu zprávu, čímž ho donutíme pracovat worker.postMessage();
Můžeme mu samosebou předat nějaké hodnoty ke zpracování:
//Vytvoříme nový objekt worker
var worker = new Worker('worker.js'); // Pošleme mu zprávu a přihodíme i hodnoty ke zpracování var info = 'Weboví dělníci'; worker.postMessage(info);
V samotném kódu Web Workera, tedy v souboru worker.js
, použijeme obsluhu události onmessage
k přijetí dat a zahájení potřebných prací. Pokud budeme ve zprávě předávat nějakou proměnnou, můžeme k ní přistupovat přes event.data
třeba takto:
// Přijmeme zprávu z hlavního vlákna onmessage = function(event) { // Něco uděláme var info = event.data; };
Posílání zpráv zpátky hlavnímu vláknu používá naprosto stejný postup ( worker.js
):
//Přijmeme zprávu z hlavního vlákna
onmessage = function(event) { // Něco uděláme var info = event.data; var result = info + ' dejme se na pochod!'; postMessage(result); };
V hlavním vlákně ošetříme došlou zprávu
//Vytvoříme nový objekt worker
var worker = new Worker('worker.js'); // Pošleme mu zprávu a přihodíme i hodnoty ke zpracování var info = 'Weboví dělníci'; worker.postMessage(info);
// Přijmeme zprávu od pomocníka worker.onmessage = function (event) { // a něco s ní uděláme alert(event.data); };
Můžete si naši malou demonstraci Web Workers stáhnout a vyzkoušet.
Opera je navržena jako jednovláknový prohlížeč kvůli podpoře různých platforem, proto současná implementace Web Workers prokládá vykonávání kódu v jednom vláknu. Jiné prohlížeče mohou mít vícevláknovou architekturu a mohou spouštět vlákna Web Workers jako oddělené procesy.
Mějte na paměti, že…
Výše uvedený příklad je velmi jednoduchý, ale když svěříte Web Workerům nějaké komplikované úlohy, jako zpracování velkých polí či výpočty souřadnic bodů v 3D prostoru, stanou se velmi užitečnou pomůckou. Důležité je pamatovat si, že výpočty spuštěné v oddělených vláknech jako Web Workers nemohou přistupovat k obsahu stránky přes DOM. Ve výše uvedeném příkladu bychom např. nemohli volat alert() přímo z kódu worker.js, ani bychom nemohli volat např. document.getElementById()
— pomocný kód může pouze přijímat a odesílat proměnné, i když proměnnou může být řetězec, číslo či jakýkoli JSON objekt.
Představení Web Workers zakončíme stručným přehledem toho, co v nich lze použít a co ne.
- Můžete použít:
- objekt
navigator
- objekt
location
(pouze ke čtení) - metodu
importScripts()
(pro přístup ke skriptům ze stejné domény) - JavaScriptové objekty, jako jsou
Object
,Array
,Date
,Math
,String
XMLHttpRequest
setTimeout()
asetInterval()
- objekt
- Nemůžete přistupovat k:
- DOMu
- Rodičovské stránce, která pomocný proces vytvořila (s výjimkou předávání dat přes
postMessage()
)
Podpora v prohlížečích
V době psaní článku podporují Web Workers pouze některé prohlížeče, takže by tato funkce měla být používána obezřetně. Namísto sledování verzí prohlížečů můžete v kódu použít snadnou metodu ověření podpory Web Workers. Stačí testovat existenci vlastnosti Worker
objektu window
:
// Zjištění podpory WebWorkers if (!!window.Worker) { // Skvěle, nudnou práci udělá někdo jiný! }
Web Workers se hodí v situacích, kdy nechceme nechat uživatele stránky zbůhdarma civět na nereagující nehybnou stránku, zatímco se na pozadí něco počítá. Namísto toho se může hlavní vlákno soustředit na práci s uživatelským rozhraním a na rychlou interakci s uživatelem, zatímco vlákna Web Workers mohou na pozadí zpracovávat data a komunikovat pomocí AJAXu se serverem. Což je skvělé a úžasné a všichni budou šťastní…
Odkazy k tématu
- Poslední verze specifikace Web Workers
- Jednoduchá ukázka Web Workers, autor: Remy Sharp
- Ukázka Web Workers – spojování bodů, autor: Brandon Aaron
- Úvod do Web Workers API, autor Nicholas C. Zakas
- Detekce podpory HTML5 a souvisejících JavaScriptových API, autor: Mark Pilgrim
- Ukázka raytracingu v JS s pomocí Web Workers
- Práce s Web Workers, Mozilla Developer Central
Tento článek je volným překladem článku Web Workers rise up!, vydaného na Dev.Opera. Autorem původního textu je Daniel Davis. Překlad vychází s laskavým svolením Opera Software.
Vzpomínám si, kdysi o tom psal Martin Hassman. Já se ho ptal, zda tam půjde předat libovolný objekt, včetně třeba window. (Tím by šlo obejít omezení na manipulaci se stránkou.) Dostal jsem (přibližně) odpovězeno, že půjde předávat pouze primitivní datové typy. Tak nevím. V Opeře mini se mi to teď nechce zkoušet.
Podle originálu by mělo jít předat, cituji: „JavaScript objects such as Object, Array, Date, Math, String.“ Objekt window pravděpodobně předat nepůjde, bylo by to nelogické – mám takové podezření, že tam někde proběhne konverze na JSON a že pod pojmem „objekt“ je tu spíš míněna „datová struktura“.
No je to zaujimava moznost a v dnesnej dome pararelneho programovania a presadzovania viacjadrovych procesorov je to aj velmi pravdepodobna cesta. Ale osobne si myslim ze vzhladom na to ako dokaze byt js zabordelovany a neprehladny pridanie este aj vytvarania threadov a pararelneho programovania v js je uplne peklo. Uz len deadlocky v js tu chybaju. Osobne si myslim ze optimalizacie a pararelne vypocty by mali stale ostat na web browseri a jeho js interprete a nie na programatorovy a jeho kode v js.
Taková optimalizace na straně prohlížeče nikdy nebude mít rovnocenné výsledky s dobře napsaným paralelním kódem. Vážně bychom měli kvůli „programátorům“ zakázat tuto možnost lépe rozložit výkon?
JS je výborný jazyk a neřekl bych, že je „zabordelovaný“. Musíte se mu jen dostat pod kůži a (ostatně jako u všech jazyků) musíte psát jako člověk ;)
Samozrejme rucne napisany kod moze byt vykonnejsi, len je otazne ci cena za to a za potencionalne problemmy je adekvatna.
Dobre, poviem to inak, JS umoznuje velmi lahko spravit chybu a tak isto pisat prasacky kod. Samozrejme to neznamena ze je to zly jazyk ale proste fakt z praxe.
však nikdo nikoho nenutí WebWorkery používat =) … když někdo uzná za vhodné, že by je měl použít, tak už by to měl být někdo kdo se v JS vyzná
Prosím, mohl byste se podělit o název programovacího jazyka, který neumožní udělat jednoduše chybu a psát kód jako prase? Ano, chyba a prasácký kód v ECMAScriptu bude pravděpodobně vypadat jinak, než to samé v C#, jenže to platí i pro C# a Pascal, pro Pascal a C,…
Je-li programátor prase, je jedno, v čem píše.
Např. silná typovost jazyka IMHO znamená menší pravděpodobnost udělání chyby…
Nikoliv, znamená stejnou pravděpodobnost udělání chyby, jenom ji compiler odhalí :)
Na druhou stranu, pokud pracujete s proměnnými typu object, pointer, variant apod. tak jako tak čert ví, co tam vůbec je :). A k tomu je potřeba pořádnost :)
Ale kompiler člověk použít MUSÍ, aby dostal spustitelný kód, kdežto v JavaScriptu není žádná kontrola vyžadována :)
Nikoli, pokud vám IDE napovídá.
Je mi líto, ale pokud máte typ pointer, IDE nemá žádnou možnost zjistit, co tam bude, až program poběží.
No, bavíme-li se o vyšších jazycích (jedna z mála podobností Javy a Javascriptu), řekněme s GC, tak tam nejsou pointery, ale reference. A kompilátor (popř. běhové prostředí) nedovolí udělat referenci nekonzistentní.
1/ jenže se můžeme bavit i o vyšších jazycích bez GC
2/ GC nemá s datovým typem a prací s tímto datovým typem nic společného
3/ i s GC se vám může vrátit pointer na datovou strukturu o ketré můžete jenom doufat, že tam je, co tam má být :)
No dobře, možná jsem špatně zobecnil. Takže, máme tyto možnosti:
* dynamicky typovaný jazyk – např. Javascript, Ruby, Python
* staticky typovaný jazyk – např. Java, C, C++, Scala
(Mixovanými jazyky jako Groovy++, C# a Mirah (aspoň v budoucnu) si situaci nebudeme komplikovat.)
a nezávisle (Má to myslím i nějaké oficiálně používané názvy, teď se mi to nechce hledat.):
* volná správa paměti (malloc apod.) – např C, C++
* striktní správa paměti (typicky s GC) – např. Java, Scala, Javascript, Ruby, Python
Zpracováním budu nazývat např. kompilaci nebo nějaké počáteční parsování apod. v závislosti na implementaci jazyka.
Asi se shodneme, že staticky typovaný jazyk má obecně větší kontrolu při zpracování než obdobný dynamicky typovaný jazyk. A podobně, jazyk se striktní správou paměti má při zpracování obecně větší kontrolu než obdobný jazyk s volnou správou paměti.
Dělat obecnější srovnání nemá asi moc smysl. Co do kontroly při zpracování, je asi jasné, že např. Java > Javascript, ale už se můžeme dohadovat, co doplnit místo otazníku do C ? Javascript.
Ještě jedna věc:
„3/ i s GC se vám může vrátit pointer na datovou strukturu o ketré můžete jenom doufat, že tam je, co tam má být :)“
To by byl asi poněkud specifický případ. GC, pokud nepoužívá ne úplně dokonalý reference counting asi nemá jinou možnost, než obsahu paměti dobře porozumnět. Ano, i za dodržení této podmínky by šlo asi vymyslet něco takového, jak říkáte, ale považuji to za extrémní.
„2/ GC nemá s datovým typem a prací s tímto datovým typem nic společného“
Jak jsem psal výše, má.
Extrémní? Nikoliv, pro někoho každodenní rutina, stačí pracovat s knihovnami, které alokují a uvolňují paměť sami, kdy si předáváte datové struktury mezi knihovnou (z pohledu kompilátoru a programového GC se jedná o blackbox) a programem a naopak.
Možná si jenom nerozumíme, samožejmě, že GC rozumí typům, ale nemá s prací s daným typem nic společného ve smyslu algoritmu programu (pokud integer, pracujete s ním stejně bez ohledu na přítomnost GC, integer se nemění na základě přítomnosti GC)
Knihovny, které paměť alokují a uvolňují samy, neřadím do GC.
Integer GC moc nezajímá, ale reference ano, včetně toho, co je na ní obsaženo. Znám dva typy GC:
* reference counting – Dobře, pro něj některá z uvedených tvrzení nemusí platit, ale na druhou stranu, tento GC si neporadí např. s cyklickými referencemi. Myslím, že se od něj dnes spíše upouští.
* Mark&sweep (včetně generačních, kopírovacích apod.) – tento tu paměť prolézat musí. Zajímají jej reference a všechny datové typy, které mohou obsahovat další reference. Proto představa nějakého pointeru, který odkazuje na neznámý typ, mi přijde zvláštní.
(Pokud existuje ještě jiný typ GC, rád se naučím něco nového. Existenci jiného typu GC jsem nevyvrátil. Na druho stranu, bavím se o dnešku, tedy to, co může být zítra, mě v tuto chvíli až tak nezajímá.)
Nemám na mysli jiný typ GC. K této diskuzi jsem se dostali z bodu, kdy jsme diskutovali, do jaké míry silná typovost, kompilátor a GC umožní odfiltrovat chyby. Odfiltrovat umožní, ale chybám v typu nezabrání. Ok odprostím se od pointeru a knihoven, uvedu jiný příklad, představte si, že máte proměnnou typu Object (od kterého jsou odvozené všechny ostatní typy objektové typy), v nějakém místě se do ní něco přiřadí (řekněme že program očekává User a „nějak“ se tam dostane DBConnection), co konkrétně tam je prostě nikdo nemůže zjistit, samozřejmě, GC to za běhu ví, a poradí si stím, protože má přehled o té proměnné, ale GC vás před chybným přiřazením neuchrání, ani kompilátor, ani nic jiného. A to byl začátek této diskuze :)
Koukám, že jsem diskutoval spíše o statickém než o silném typování. Oboje pomáhá proti chybám. Že to není 100% prostředek proti nim je snad jasné.
Reagoval jsem na příspěvek pana Augustýna z 11. 8. 2010 22:38 a na Váš z 12. 8. 2010 2:03, které naznačovaly, že snad nějaké takové prostředky existují :)
To jsem naznačit nechtěl, měl jsem namysli pouze snížení pravděpodobnosti chyby, ne odstranění možnosti chyby. Hlavně že si rozumíme.
jen drobnost, nedělejte rovnítko mezi pointer a referenci. Pointer je datový typ obsahující adresu v paměti. Prakticky se jedná o číslo, tohle samozřejmě kompilátor pohlídá, ale nepohlídá, co je na daném místě v paměti. Prakticky se dostáváte do pozice slabě typového jazyka: máte proměnnou, ale nevíte co se za jí prakticky ukrývá (jinak řečeno, víte, že p je pointer, ale co je v p^ nevíte a kompilátor vám to nepoví)
Jo, to vím a myslím, že mezi pointer a referenci jsem zde rovnítko nedával. Nebo ano?
„No, bavíme-li se o vyšších jazycích (jedna z mála podobností Javy a Javascriptu), řekněme s GC, tak tam nejsou pointery, ale reference.“
Pointer je datový typ, který může existovat i v prostření s GC i ve vyšších jazycích. Resp zeptám se jinak, proč se podle vás pointer a reference vylučují? (možná jsem se netrefil, znám několik významů slova reference ve spojení s programovacími jazyky, kompilátory/interprety a GC)
Tak, pointer je podle mé představy nástroj, který dává programátorovi maximální volnost a za to od něj vyžaduje maximální odpovědnost. Například v C mohu mít pointer na nějaké místo, k němu přičíst nějaké číslo (automaticky vynásobené příslušným sizeof) a dostat se úplně jinam. GC nemá jak zjistit, že je daný kus paměti používán. Asi by to šlo nějakou dohodou s GC „na toto mi nesahej“, něco takového má AFAIK D, ale zatím jsem se s tím moc nesetkal.
Tedy je asi pravda, že jsem se dopustil určité nepřesnosti, na druhou stranu, podstatná část „Striktní a statické typování pomáhají statické kontrole, nejlepší je na to oboje dohromady.“ tu zůstává.
tak tady 2× souhlas :) GC ví o pointru, ale neví, co tam na programátora čeká :)
a ano, statické a striktní typování pomáhá, hodně pomáhá :). Proto se i ECMAScriptu snažím dodržovat některá základní pravila: všechny proměnné jsou deklarované na začátku bloku platnosti a mají přiřazenou výchozí hodnotu (aby bylo poznat, k čemu jsou, výjimkou jsou proměnné jako i, j, k, l… které používám pouze jako iterátory [for cyklus] a nikdy jinak), žádnou proměnnou nerecykluji, je-li použita jednou jako to string, musí to být string po celou dobu platosti :))
Ano, takže přidáme typy parametrů a můžeme dělat type inference jako ve Scale, Mirah a Groovy++. Mimochodem, váš přístup je dobrý i pro výkon. Mám pocit, že mnohé dnešní implementace JS používají type inference jako implementační detail, takže k function(a, b){return a+b;} mohou časem za běhu vzniknout varianty jako function(String a, String b){return a+b;}, function(int a, int b){return a+b;} apod., které jsou vevnitř staticky typované a lépe optimalizované.
Tak ano, pokud je parser/interpret výkonný a nezaznamená například v daném kontextu eval() popřípadě new Function() (kterým se vyhýbám jako čert kříži), může interně při interpretaci vytvořit staticky typované reprezentace.
Nějaká forma pro začátek alespoň type hintingu by pomohla, ale změnit najedou ECMAScript na silně a/nebo staticky typovaný jazyk by byla dost výrazná změna jazyka.
je možné vložit webWorker do webWorkeru?
Pokud ne přímo, pak by to teoreticky mělo jít tak, že mu to umožní obklopující stránka.
Jak „přímo vložit“? Přímo vložit nelze – co WebWorker, to vlastní soubor. Ale je možné, že se tazatel špatně vyjádřil, a chtěl se zeptat na vyvolávání workeru z workerů – to by mělo bez problémů jít – v hlavním vláknu zavolat jako workera work1.js, a ten si zavolá workera work2.js… :)
To „vložení“ jsem pochopil jako ono „volání“. Poznamenal jsem, že i kdyby to nebyl přímo podporováno, tak by to šlo obejít.
Pochopil jsem to stejně, jen jsem to možná poslal jako odpověď k jiné otázce. Pokud ano, omlouvám se.
Bylo to myleno jako volání workeru z workeru
Tak to by mělo jít, ale raději vyzkoušejte…