Wielowątkowość w przeglądarce (Web Workers + jQuery)

Przed nami ciekawe czasy w tech­no­lo­gii WWW. Powoli wcho­dzi HTML5 wpro­wa­dza­jąc całko­wi­cie nową jakość. Póki co strony w HTML5 mają charak­ter bardziej poka­zowy niż użyteczny. Ja jako maniak tech­no­lo­gii musia­łem zacząć się z tym wszyst­kim bawić. Na pierw­szy ogień idzie tech­no­lo­gia Web Workers [dalej WW].

WW to próba wpro­wa­dze­nia wielo­wąt­ko­wo­ści w prze­glą­darce WWW. W zało­że­niu zada­nia, które wyma­gają dużej mocy obli­cze­nio­wej wrzu­cane są do takiego workera. Gdy sobie skoń­czy liczyć, powia­da­mia prze­glą­darkę, a ta wyświe­tla wynik. Chodzi o to, że użyt­kow­nik strony nieko­niecz­nie ma ochotę cier­pieć zwol­nie­nie pracy prze­glą­darki w czasie kiedy proce­sor jest obcią­żony (ładnie się to nazywa respon­syw­ność przeglądarki).

Zresztą to nie jest jakaś super nowość. Z wątkami w tle spoty­ka­cie się na co dzień prze­glą­da­jąc Face­bo­oka (powia­do­mie­nia kto jest na czacie, nowe posty na tablicy bez prze­ła­do­wy­wa­nia itd.) Oficjalne API WW ma spore obostrze­nia. Worker to jakby zamknięte środo­wi­sko. Nie mamy dostępu do np. obiek­tów strony macie­rzy­stej (DOM, jQuery). Więcej pole­cam poczy­tać u Marco­osa.

Dzisiaj zrobimy coś, czego nie widzia­łem nigdzie w Inter­ne­cie póki co. Spró­bu­jemy dyna­micz­nie gene­ro­wać nowe wątki, kaso­wać je, a nawet regu­lo­wać często­tli­wość ich odpo­wie­dzi. Wszystko to będzie oczy­wi­ście ładnie okra­szone biblio­teką jQuery. Siedzia­łem nad tym pół niedzieli, więc uważajcie!

Najpierw rzuć­cie okiem na efekt finalny, a poni­żej wyja­śnie­nia co ciekaw­szych rzeczy.

Pierw­sze co robimy to spraw­dzamy czy prze­glą­darka użyt­kow­nika w ogóle obsłu­guje WW. Korzy­stamy z biblio­teki Moder­nizr i wyświe­tlamy alert informujący.

Druga sprawa to kod samego workera (timeworker.js):

var workerNo;
 
onmessage = function(e) {
    workerNo = e.data.workerNo;
    changeInterval(e.data.timer);
}
 
function changeInterval(interval) {
    setInterval("gimmeTime()",interval);
}
 
function gimmeTime() {
    postMessage({
        time:new Date().toUTCString(),
        workerNo:workerNo
    });
}
  1. Pole workerNo zawiera iden­ty­fi­ka­tor workera — liczbę od 1 w górę.
  2. onmes­sage to komu­ni­kat odebrany od prze­glą­darki. Zwra­camy uwagę na to, że do odebra­nych danych dobie­ramy się poprzez e.data, a nie samo e!
  3. chan­ge­In­te­rval to funk­cja usta­wia­jąca samo­wy­wo­ła­nie okre­ślo­nej funk­cji cyklicz­nie co podany czas w ms.
  4. gimme­Time to funk­cja wysy­ła­jące prze­glą­darce JSON z aktu­al­nym czasem i swoim iden­ty­fi­ka­to­rem za pomocą funk­cji post­Mes­sage().

Teraz czas na kod workersdemo.html (podaję sam kod Java­Script bo szkoda miejsca).

var workers = new Array();
var licznik = 0;
var bindedWorkers = '';
 
if (Modernizr.webworkers) {
    alert('Twoja przeglądarka wspiera Web Workers, możesz śmiało testować!');
}
else {
    alert('Twoja przeglądarka NIE wspiera Web Workers, demo nie będzie działało. Zainstaluj sobie prawdziwą przeglądarkę (FF, Chrome, Opera[?])!');
}
 
function addNotification(workerno) {
    var msg = $('<p>').appendTo('#notif');
    msg.text('Worker '+workerno+' updated state');
    msg.fadeOut(1500);
}
 
function fireUpWorker() {
    ++licznik;
    var workerDiv = $('<div>').attr('id','workerdiv-'+licznik).appendTo('#liczniki');
    var par = $('<p>').attr('id','worker-'+licznik).appendTo(workerDiv);
    $('<input />').attr({
        type:'submit',
        value:'terminate',
        onClick:'terminateWorker('+licznik+');return false;'
    }).appendTo(workerDiv);
    workers[licznik] = new Worker('timeworker.js');
    var timer = $('#intrv').val();
    workers[licznik].postMessage({timer:timer,workerNo:licznik});
    bindWorkers();
}
 
function terminateWorker(workerNo) {
    workers[workerNo].terminate();
    $('#worker-'+workerNo).text('terminated!');
    $('#workerdiv-'+workerNo).fadeOut(5000);
}
 
function bindWorkers() {
    bindedWorkers += "workers["+licznik+"].onmessage = function(event) {";
    bindedWorkers += "$('#worker-"+licznik+"').text('Worker '+event.data.workerNo+' date : '+event.data.time);";
    bindedWorkers += "addNotification(event.data.workerNo);";
    bindedWorkers += "};";
    eval(bindedWorkers);
}
  1. Mamy 3 pola globalne: tablicę workers trzy­ma­jącą wszyst­kich aktyw­nych worke­rów, licz­nik worke­rów i binde­dWor­kers zawie­ra­jący brzydki kod Java­Script do wyko­na­nia poprzez eval() — jedyny znany mi sposób na zmusze­nie prze­glą­darki żeby słuchała wszyst­kich aktyw­nych worke­rów, a nie tylko ostat­niego dodanego.
  2. addNo­ti­fi­ca­tion wyrzuca użyt­kow­ni­kowi komu­ni­kat o odebra­niu wiado­mo­ści od workera, znika po 1,5 sek.
  3. fireU­pWor­ker (nie chciało mi się wysi­lać na lepszą nazwę ;-) ) to główna funk­cja. Najpierw tworzy nowe elementy HTML: div, w nim para­graf na komu­ni­kat z workera i przy­cisk zabi­ja­jący. Nowo stwo­rzo­nemu worke­rowi wysy­łam komu­ni­kat w JSON co jaki czas ma się odświe­żać i jaki ma numer. Na koniec wywo­łuję funk­cję bindWor­kers().
  4. termi­na­te­Wor­ker najpierw zabija okre­ślo­nego workera poprzez worker.terminate(), a następ­nie ukrywa zwią­za­nego z nim diva.
  5. Na koniec funk­cja, z której jestem najmniej zado­wo­lony: bindWor­kers(). Zajmuje się usta­wia­niem co prze­glą­darka ma robić w przy­padku otrzy­ma­nia komu­ni­katu od workera. Działa to analo­gicz­nie jak poprzed­nio. Poprzez onmes­sagee.data. Do pola binde­dWor­kers doda­jemy kod w postaci ciągu i urucha­miamy evalem.

Tutaj mam prośbę dla znaw­ców. Doda­wa­nie onmes­sage w pętli dla poszcze­gól­nych worke­rów osobno nie dzia­łało. Jak zała­twić problem bez używa­nia evala?

P.S.: Znala­złem chyba buga w Chrome: Próby doda­nia atry­butu onclick poprzez jQuery nic nie dają. Należy używać onClick :-)

Podobne wpisy:

  1. Multi­th­re­ading in web brow­ser (Web Workers + jQuery)
  2. Atak klonów w PHP
  3. Zapi­sy­wa­nie plików flash oglą­da­nych w przeglądarce
  4. Weź mi zrób proce­sor tekstu cz. 1 / 2

One comment

  • 20 lutego 2011 - 21:09 | Permalink

    Świetny art! A co powiesz o tym — krót­sza wersja:

    http://eriz.pcinside.pl/weblog/ajax-w-jquery-i-callbacki-javascript-218.html

  • Dodaj komentarz

    Twój adres e-mail nie zostanie opublikowany.

    Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <p> <pre lang="" line="" escaped=""> <q cite=""> <strike> <strong>