C'è un modo per fare il multi-threading in JavaScript?
C'è un modo per fare il multi-threading in JavaScript?
Risposte:
Vedi http://caniuse.com/#search=worker per le informazioni di supporto più aggiornate.
Quello che segue è stato lo stato di supporto intorno al 2009.
Le parole per cui vuoi cercare Google sono Discussioni dei lavoratori JavaScript
Oltre a Gears non c'è nulla di disponibile in questo momento, ma ci sono molti discorsi su come implementarlo, quindi immagino che guardi questa domanda poiché la risposta cambierà senza dubbio in futuro.
Ecco la documentazione pertinente per Gears: WorkerPool API
WHATWG ha una bozza di raccomandazione per i thread di lavoro: Web Workers
E c'è anche il thread di lavoro DOM di Mozilla
Aggiornamento: giugno 2009, stato attuale del supporto del browser per i thread JavaScript
Firefox 3.5 ha operatori Web. Alcune demo di web worker, se vuoi vederle in azione:
Il plugin Gears può essere installato anche in Firefox.
Safari 4 e i nightly di WebKit hanno thread di lavoro:
Chrome ha Gears integrato, quindi può fare discussioni, anche se richiede una richiesta di conferma da parte dell'utente (e utilizza un'API diversa per gli operatori Web, sebbene funzionerà in qualsiasi browser con il plug-in Gears installato):
IE8 e IE9 possono eseguire discussioni solo con il plug-in Gears installato
Prima di HTML5 JavaScript consentiva solo l'esecuzione di un thread per pagina.
Ci fosse un modo hacky per simulare un'esecuzione asincrona con Yield , setTimeout()
, setInterval()
, XMLHttpRequest
o gestori di eventi (vedere la fine di questo post per un esempio con resa e setTimeout()
).
Ma con HTML5 ora possiamo usare i thread di lavoro per parallelizzare l'esecuzione delle funzioni. Ecco un esempio di utilizzo.
HTML5 ha introdotto i thread di lavoro Web (vedi: compatibilità dei browser )
Nota: IE9 e le versioni precedenti non lo supportano.
Questi thread di lavoro sono thread JavaScript eseguiti in background senza influire sulle prestazioni della pagina. Per ulteriori informazioni su Web Worker leggi la documentazione o questa esercitazione .
Ecco un semplice esempio con 3 thread di Web Worker che contano fino a MAX_VALUE e mostrano il valore calcolato corrente nella nostra pagina:
//As a worker normally take another JavaScript file to execute we convert the function in an URL: http://stackoverflow.com/a/16799132/2576706
function getScriptPath(foo){ return window.URL.createObjectURL(new Blob([foo.toString().match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1]],{type:'text/javascript'})); }
var MAX_VALUE = 10000;
/*
* Here are the workers
*/
//Worker 1
var worker1 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
//We add a listener to the worker to get the response and show it in the page
worker1.addEventListener('message', function(e) {
document.getElementById("result1").innerHTML = e.data;
}, false);
//Worker 2
var worker2 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
worker2.addEventListener('message', function(e) {
document.getElementById("result2").innerHTML = e.data;
}, false);
//Worker 3
var worker3 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
worker3.addEventListener('message', function(e) {
document.getElementById("result3").innerHTML = e.data;
}, false);
// Start and send data to our worker.
worker1.postMessage(MAX_VALUE);
worker2.postMessage(MAX_VALUE);
worker3.postMessage(MAX_VALUE);
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
Possiamo vedere che i tre thread vengono eseguiti in concorrenza e stampano il loro valore corrente nella pagina. Non congelano la pagina perché vengono eseguiti in background con thread separati.
Un altro modo per raggiungere questo obiettivo è utilizzare più iframe , ognuno eseguirà un thread. Possiamo fornire all'iframe alcuni parametri tramite l'URL e l' iframe può comunicare con il suo genitore per ottenere il risultato e stamparlo (l' iframe deve trovarsi nello stesso dominio).
Questo esempio non funziona in tutti i browser! gli iframe di solito vengono eseguiti nello stesso thread / processo della pagina principale (ma Firefox e Chromium sembrano gestirli in modo diverso).
Poiché lo snippet di codice non supporta più file HTML, fornirò semplicemente i diversi codici qui:
index.html:
//The 3 iframes containing the code (take the thread id in param)
<iframe id="threadFrame1" src="thread.html?id=1"></iframe>
<iframe id="threadFrame2" src="thread.html?id=2"></iframe>
<iframe id="threadFrame3" src="thread.html?id=3"></iframe>
//Divs that shows the result
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
<script>
//This function is called by each iframe
function threadResult(threadId, result) {
document.getElementById("result" + threadId).innerHTML = result;
}
</script>
thread.html:
//Get the parameters in the URL: http://stackoverflow.com/a/1099670/2576706
function getQueryParams(paramName) {
var qs = document.location.search.split('+').join(' ');
var params = {}, tokens, re = /[?&]?([^=]+)=([^&]*)/g;
while (tokens = re.exec(qs)) {
params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
}
return params[paramName];
}
//The thread code (get the id from the URL, we can pass other parameters as needed)
var MAX_VALUE = 100000;
(function thread() {
var threadId = getQueryParams('id');
for(var i=0; i<MAX_VALUE; i++){
parent.threadResult(threadId, i);
}
})();
Il modo 'ingenuo' sarebbe di eseguire la funzione setTimeout()
una dopo l'altra in questo modo:
setTimeout(function(){ /* Some tasks */ }, 0);
setTimeout(function(){ /* Some tasks */ }, 0);
[...]
Ma questo metodo non funziona perché ogni attività verrà eseguita una dopo l'altra.
Possiamo simulare l'esecuzione asincrona chiamando la funzione in modo ricorsivo in questo modo:
var MAX_VALUE = 10000;
function thread1(value, maxValue){
var me = this;
document.getElementById("result1").innerHTML = value;
value++;
//Continue execution
if(value<=maxValue)
setTimeout(function () { me.thread1(value, maxValue); }, 0);
}
function thread2(value, maxValue){
var me = this;
document.getElementById("result2").innerHTML = value;
value++;
if(value<=maxValue)
setTimeout(function () { me.thread2(value, maxValue); }, 0);
}
function thread3(value, maxValue){
var me = this;
document.getElementById("result3").innerHTML = value;
value++;
if(value<=maxValue)
setTimeout(function () { me.thread3(value, maxValue); }, 0);
}
thread1(0, MAX_VALUE);
thread2(0, MAX_VALUE);
thread3(0, MAX_VALUE);
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
Come puoi vedere, questo secondo metodo è molto lento e congela il browser perché utilizza il thread principale per eseguire le funzioni.
Rendimento è una nuova funzionalità di ECMAScript 6 , funziona solo con la versione più vecchia di Firefox e Chrome (in Chrome è necessario abilitare JavaScript sperimentale che appare in chrome: // flags / # enable-javascript-harmony ).
La parola chiave yield rende l'esecuzione della funzione generatore in pausa e il valore dell'espressione che segue la parola chiave yield viene restituito al chiamante del generatore. Può essere pensato come una versione basata sul generatore della parola chiave return.
Un generatore consente di sospendere l'esecuzione di una funzione e riprenderla in un secondo momento. Un generatore può essere utilizzato per programmare le tue funzioni con una tecnica chiamata trampolino .
Ecco l'esempio:
var MAX_VALUE = 10000;
Scheduler = {
_tasks: [],
add: function(func){
this._tasks.push(func);
},
start: function(){
var tasks = this._tasks;
var length = tasks.length;
while(length>0){
for(var i=0; i<length; i++){
var res = tasks[i].next();
if(res.done){
tasks.splice(i, 1);
length--;
i--;
}
}
}
}
}
function* updateUI(threadID, maxValue) {
var value = 0;
while(value<=maxValue){
yield document.getElementById("result" + threadID).innerHTML = value;
value++;
}
}
Scheduler.add(updateUI(1, MAX_VALUE));
Scheduler.add(updateUI(2, MAX_VALUE));
Scheduler.add(updateUI(3, MAX_VALUE));
Scheduler.start()
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
Con le "specifiche laterali" HTML5 non è più necessario hackerare javascript con setTimeout (), setInterval (), ecc.
HTML5 & Friends introduce i Web Worker javascript specifica . È un'API per l'esecuzione di script in modo asincrono e indipendente.
Collegamenti alle specifiche e un tutorial .
Non esiste un vero threading in JavaScript. JavaScript è la lingua malleabile che è, ti permette di emularne una parte. Ecco un esempio che mi sono imbattuto l'altro giorno.
Ecco solo un modo per simulare il multi-threading in Javascript
Ora ho intenzione di creare 3 thread che calcoleranno l'aggiunta di numeri, i numeri possono essere divisi con 13 e i numeri possono essere divisi con 3 fino a 10000000000. E queste 3 funzioni non sono in grado di eseguire contemporaneamente al significato di Concurrency. Ma ti mostrerò un trucco che farà funzionare queste funzioni in modo ricorsivo nello stesso tempo: jsFiddle
Questo codice mi appartiene.
Parte del corpo
<div class="div1">
<input type="button" value="start/stop" onclick="_thread1.control ? _thread1.stop() : _thread1.start();" /><span>Counting summation of numbers till 10000000000</span> = <span id="1">0</span>
</div>
<div class="div2">
<input type="button" value="start/stop" onclick="_thread2.control ? _thread2.stop() : _thread2.start();" /><span>Counting numbers can be divided with 13 till 10000000000</span> = <span id="2">0</span>
</div>
<div class="div3">
<input type="button" value="start/stop" onclick="_thread3.control ? _thread3.stop() : _thread3.start();" /><span>Counting numbers can be divided with 3 till 10000000000</span> = <span id="3">0</span>
</div>
Parte Javascript
var _thread1 = {//This is my thread as object
control: false,//this is my control that will be used for start stop
value: 0, //stores my result
current: 0, //stores current number
func: function () { //this is my func that will run
if (this.control) { // checking for control to run
if (this.current < 10000000000) {
this.value += this.current;
document.getElementById("1").innerHTML = this.value;
this.current++;
}
}
setTimeout(function () { // And here is the trick! setTimeout is a king that will help us simulate threading in javascript
_thread1.func(); //You cannot use this.func() just try to call with your object name
}, 0);
},
start: function () {
this.control = true; //start function
},
stop: function () {
this.control = false; //stop function
},
init: function () {
setTimeout(function () {
_thread1.func(); // the first call of our thread
}, 0)
}
};
var _thread2 = {
control: false,
value: 0,
current: 0,
func: function () {
if (this.control) {
if (this.current % 13 == 0) {
this.value++;
}
this.current++;
document.getElementById("2").innerHTML = this.value;
}
setTimeout(function () {
_thread2.func();
}, 0);
},
start: function () {
this.control = true;
},
stop: function () {
this.control = false;
},
init: function () {
setTimeout(function () {
_thread2.func();
}, 0)
}
};
var _thread3 = {
control: false,
value: 0,
current: 0,
func: function () {
if (this.control) {
if (this.current % 3 == 0) {
this.value++;
}
this.current++;
document.getElementById("3").innerHTML = this.value;
}
setTimeout(function () {
_thread3.func();
}, 0);
},
start: function () {
this.control = true;
},
stop: function () {
this.control = false;
},
init: function () {
setTimeout(function () {
_thread3.func();
}, 0)
}
};
_thread1.init();
_thread2.init();
_thread3.init();
Spero che in questo modo sarà utile.
Puoi usare Narrative JavaScript , un compilatore che trasformerà il tuo codice in una macchina a stati, permettendoti di emulare il thread in modo efficace. Lo fa aggiungendo un operatore "cedente" (indicato come '->') al linguaggio che consente di scrivere codice asincrono in un singolo blocco di codice lineare.
Il nuovo motore v8 che dovrebbe uscire oggi lo supporta (penso)
In Javascript non elaborato, il meglio che puoi fare è usare le poche chiamate asincrone (xmlhttprequest), ma non è proprio thread e molto limitato. Google Gears aggiunge una serie di API al browser, alcune delle quali possono essere utilizzate per il supporto dei thread.
Se non puoi o non vuoi usare alcun materiale AJAX, usa un iframe o dieci! ;) Puoi avere processi in esecuzione in iframe in parallelo con la pagina principale senza preoccuparti di problemi comparabili tra browser o problemi di sintassi con dot net AJAX ecc. E puoi chiamare il JavaScript della pagina principale (incluso il JavaScript che ha importato) da un iframe.
Ad esempio, in un iframe principale, per chiamare egFunction()
il documento principale una volta caricato il contenuto dell'iframe (questa è la parte asincrona)
parent.egFunction();
Generare dinamicamente anche gli iframe in modo che il codice html principale sia libero da essi, se lo si desidera.
Un altro possibile metodo sta usando un interprete javascript nell'ambiente javascript.
Creando più interpreti e controllandone l'esecuzione dal thread principale, è possibile simulare il multi-threading con ciascun thread in esecuzione nel proprio ambiente.
L'approccio è in qualche modo simile ai web worker, ma dai all'interprete l'accesso all'ambiente globale del browser.
Ho realizzato un piccolo progetto per dimostrarlo .
Una spiegazione più dettagliata in questo post del blog .
Javascript non ha discussioni, ma abbiamo dei lavoratori.
I lavoratori possono essere una buona scelta se non hai bisogno di oggetti condivisi.
La maggior parte delle implementazioni del browser in realtà diffonderà i lavoratori su tutti i core, consentendo di utilizzare tutti i core. Puoi vedere una demo di questo qui .
Ho sviluppato una libreria chiamata task.js che lo rende molto facile da fare.
task.js Interfaccia semplificata per l'esecuzione del codice ad alta intensità di CPU su tutti i core (node.js e web)
Un esempio sarebbe
function blocking (exampleArgument) {
// block thread
}
// turn blocking pure function into a worker task
const blockingAsync = task.wrap(blocking);
// run task on a autoscaling worker pool
blockingAsync('exampleArgumentValue').then(result => {
// do something with result
});
Con la specifica HTML5 non è necessario scrivere troppo JS per lo stesso o trovare alcuni hack.
Una delle funzionalità introdotte in HTML5 è Web Workers che è JavaScript in esecuzione in background, indipendentemente da altri script, senza influire sulle prestazioni della pagina.
È supportato in quasi tutti i browser:
Chrome - 4.0+
IE - 10.0+
Mozilla - 3.5+
Safari - 4.0+
Opera - 11.5+