JavaScript Web Workers: A Beginner ' s Guide
Nel 2019, l’ecosistema web si è evoluto al punto in cui il browser è un ambiente di esecuzione per applicazioni basate su JavaScript. Ciò si riflette nella velocità con cui l’industria si presenta con nuovi framework, paradigmi, caricatori di moduli e bundler, gestori di dipendenze, strumenti di creazione e gestori di pacchetti anno dopo anno.
Quando JavaScript è stato concepito nei primi giorni di Internet, la direzione dello sviluppo di Internet non era chiara. A causa del costante e rapido cambiamento nel settore e nell’ecosistema, della necessità di retrocompatibilità dei browser e degli standard Web, l’evoluzione di JavaScript è diventata un flusso costante di patch, hack e ripensamenti.
I dispositivi mobili di oggi normalmente sono dotati di 8+ core CPU o 12+ core GPU. Le CPU desktop e server hanno fino a 16 core, 32 thread o più.
In questo ambiente, avere un ambiente di programmazione o scripting dominante che è single-threaded è un collo di bottiglia.
JavaScript è Single-threaded
Ciò significa che in base alla progettazione, i motori JavaScript — originariamente i browser — hanno un thread principale di esecuzione e, per dirla semplicemente, il processo o la funzione B non possono essere eseguiti fino al termine del processo o della funzione A. L’interfaccia utente di una pagina Web non risponde a qualsiasi altra elaborazione JavaScript mentre è occupata dall’esecuzione di qualcosa — questo è noto come blocco DOM.
Questo è terribilmente inefficiente, specialmente rispetto ad altre lingue.
Se andiamo a JS Bin ed eseguiamo questo codice nella console JavaScript del browser:
//noprotecti = 0;while (i < 60000) { console.log("The number is " + i); i++;}
… il tutto jsbin.com sito web diventerà non risponde fino a quando il browser conta-e registri – a 60.000.
Non saremo in grado di interagire con nulla sulla pagina, perché il browser è occupato.
Ora, questo è un processo di calcolo relativamente poco impegnativo, e le applicazioni web di oggi spesso comportano compiti molto più impegnativi.
Dobbiamo essere in grado di calcolare le cose in background mentre l’utente interagisce perfettamente con la pagina.
Web Workers
Il W3C ha pubblicato una prima bozza dello standard web workers nel 2009. La specifica completa può essere trovata sul sito Web Hypertext Application Technology Working Group – o WHATWG-un corpo di standard Web alternativo a W3C.
Web worker è un sistema asincrono, o protocollo, per le pagine Web per eseguire attività in background, indipendentemente dal thread principale e dall’interfaccia utente del sito web. È un ambiente isolato che è isolato dall’oggettowindow
, dall’oggettodocument
, dall’accesso diretto a Internet ed è più adatto per attività computazionali di lunga durata o impegnative.
Oltre ai web worker — un sistema dedicato al multithreading — ci sono altri modi per ottenere un’elaborazione asnychronous in JavaScript, come le chiamate Ajax asincrone e il ciclo degli eventi.
Per dimostrarlo, torneremo a JS Bin e proveremo questo snippet:
console.log("A");setTimeout(function(){console.log("B");},2000);console.log("C");setTimeout(function(){console.log("D");},0);console.log("E");setTimeout(function(){console.log("F");},1000);
Quando eseguiamo questo, la nostra sequenza di log è A, C, E, D, F, B
. Il browser pianifica prima le operazioni senza il timeout, così come vengono, quindi esegue le funzionisetTimeout()
nell’ordine dei rispettivi ritardi specificati. Tuttavia, questa asincronicità non dovrebbe essere automaticamente confusa con il multithreading. A seconda della macchina host, questo può spesso essere solo uno stack a thread singolo delle chiamate nell’ordine che abbiamo spiegato.
Web Workers&Multithreading
Come spiega il sito Web di riferimento JavaScript di Mozilla, i web worker sono un “mezzo per il contenuto Web per eseguire script in thread in background.”
Li usiamo nel modo seguente: controlliamo la disponibilità del costruttoreWorker()
nel browser e, se disponibile, istanziiamo un oggetto worker, con l’URL dello script come argomento. Questo script verrà eseguito su un thread separato.
Lo script deve essere servito dallo stesso host o dominio per motivi di sicurezza, e questo è anche il motivo per cui i lavoratori web non funzioneranno se apriamo il file localmente con uno schema file://
.
if (typeof(Worker) !== "undefined") { worker = new Worker("worker.js");}
Ora definiamo questo codice nel file worker.js
:
i = 0;while (i < 200000) { postMessage("Web Worker Counter: " + i); i++;}
Se vuoi scrivere file web worker JavaScript di alta qualità, controlla il nostro libro, JavaScript: Best Practice.
La separazione dei thread
Una cosa importante da notare qui è la separazione dell’ambito di esecuzionewindow
edocument
nel thread della finestra principale del browser e dell’ambitoworker
.
Per poter utilizzare il thread worker
, questi due ambiti devono essere in grado di comunicare. Per ottenere ciò, usiamo la funzionepostMessage()
all’interno del fileworker.js
— per inviare messaggi al thread principale del browser — e ilworker.onmessage
listener nel thread principale per ascoltare i messaggiworker
.
Possiamo anche inviare messaggi dal thread principale del browser al thread o alla funzione worker
. L’unica differenza è che invertiamo le cose e chiamiamo worker.postMessage()
sul thread principale e onmessage
sul thread di lavoro. Per citare riferimento sviluppatore di Mozilla:
Si noti che
onmessage
epostMessage()
devono essere appesi all’oggettoWorker
quando viene utilizzato nel thread principale dello script, ma non quando viene utilizzato nel worker. Questo perché, all’interno del lavoratore, il lavoratore è effettivamente l’ambito globale.
Possiamo usare il metodoterminate()
allo stesso modo, per terminare l’esecuzione del nostro lavoratore.
Con tutto questo in mente, arriviamo a questo esempio:
index.html
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>Web Workers Example</title> <style type="text/css"> body {padding-top:28px;} .output-cont {margin-left:12%; margin-top:28px;} .output-cont h3 {width:200px; height:100%;} .output-cont button {padding:4px 8px; font-size:1.1rem; font-family:sans-serif; } </style></head><body><div class="output-cont"><button onclick="testWorker()">start worker</button><h3></h3><button onclick="terminateWorker()">terminate worker</button></div><br/><div class="output-cont"><button onclick="testMainThread()">start blocking thread</button><h3></h3></div><br/><div class="output-cont"><button onclick="alert('browser responsive!')">test browser responsiveness</button></div> <script> var worker; function testWorker() { if (typeof(Worker) !== "undefined") { if (typeof(worker) == "undefined") { worker = new Worker("worker.js"); } worker.onmessage = function(event) { document.getElementById("workerOutput").innerHTML = event.data; }; } else { document.getElementById("workerOutput").innerHTML = "Web Workers are not supported in your browser"; } } function terminateWorker() { worker.terminate(); worker = undefined; } function testMainThread() { for (var i = 0; i < 200000; i++) { document.getElementById("mainThreadOutput").innerHTML = "Main Thread Counter: " + i; } } </script></body></html>
e lavoratore.js:
i = 0;while (i < 200000) { postMessage("Web Worker Counter: " + i); i++;}
Questo ci dà l’opportunità di testare gli effetti dell’esecuzione del thread principale sul comportamento e le prestazioni della pagina rispetto agli effetti del web worker.
In questo tutorial, abbiamo usato http-server
per servire i file localmente.
Ora possiamo vedere che il thread di lavoro non blocca l’interattività del processo principale del browser e il loop di 200.000 numeri non influisce sul thread principale. I numeri nell’elemento#workerOutput
vengono aggiornati ad ogni iterazione.
Il thread di blocco, o thread principale, quando è impegnato in un ciclo, blocca tutta l’interattività (abbiamo impostato il numero di iterazioni su 200.000 qui, ma sarà ancora più ovvio se lo aumentiamo a 2.000.000).
Un’altra cosa che ci punta a un thread principale bloccato è che il processo di lavoro aggiorna la pagina ad ogni iterazione e il ciclo nel thread principale (quello definito in index.html
) aggiorna solo l’elemento #mainThreadOutput
sull’ultima iterazione.
Questo perché il browser è troppo consumato con il conteggio (for
loop) per essere in grado di ridisegnare il DOM, quindi lo fa solo una volta che la sua attività con il ciclofor
è completamente eseguita (alla fine del ciclo).
Conclusione
In questo articolo, abbiamo introdotto web workers, una tecnologia che aiuta l’industria web a tenere il passo con applicazioni web sempre più esigenti. Questo viene fatto fornendo un modo per le app Web di sfruttare i dispositivi multi-processore e multi-thread conferendo alcuni superpoteri multi-thread a JavaScript.
I web worker trasformano gli ambienti browser mobile e desktop in piattaforme applicative, fornendo loro un ambiente di esecuzione rigoroso. Questo rigore può costringerci a prevedere la copia di oggetti tra più thread e a pianificare le nostre applicazioni tenendo conto di questi vincoli.