Existe-t-il un moyen de faire du multi-threading en JavaScript?
Existe-t-il un moyen de faire du multi-threading en JavaScript?
Réponses:
Consultez http://caniuse.com/#search=worker pour obtenir les informations d'assistance les plus récentes.
Ce qui suit était l'état du soutien vers 2009.
Les mots que vous souhaitez rechercher sur Google sont des threads de travail JavaScript
En dehors de Gears, il n'y a rien de disponible pour le moment, mais il y a beaucoup de discussions sur la façon de l'implémenter, alors je suppose que regarder cette question car la réponse changera sans doute à l'avenir.
Voici la documentation pertinente pour Gears: API WorkerPool
WHATWG a un projet de recommandation pour les threads de travail: Web Workers
Et il y a aussi les threads de travail DOM de Mozilla
Mise à jour: juin 2009, état actuel du support du navigateur pour les threads JavaScript
Firefox 3.5 a des web workers. Quelques démos de web workers, si vous voulez les voir en action:
Le plugin Gears peut également être installé dans Firefox.
Safari 4 et les nightlies WebKit ont des threads de travail:
Chrome a intégré Gears, il peut donc créer des threads, bien qu'il nécessite une invite de confirmation de la part de l'utilisateur (et qu'il utilise une API différente pour les travailleurs Web, bien qu'il fonctionnera dans n'importe quel navigateur avec le plug-in Gears installé):
IE8 et IE9 ne peuvent faire des threads qu'avec le plugin Gears installé
Avant HTML5, JavaScript n'autorisait l'exécution que d'un fil par page.
Il y avait une certaine façon hacky pour simuler une exécution asynchrone avec rendement , setTimeout()
, setInterval()
, XMLHttpRequest
ou gestionnaires d'événements (voir la fin de ce post pour un exemple avec le rendement et setTimeout()
).
Mais avec HTML5, nous pouvons désormais utiliser Worker Threads pour paralléliser l'exécution des fonctions. Voici un exemple d'utilisation.
HTML5 a introduit les threads Web Worker (voir: compatibilités des navigateurs )
Remarque: IE9 et les versions antérieures ne le prennent pas en charge.
Ces threads de travail sont des threads JavaScript qui s'exécutent en arrière-plan sans affecter les performances de la page. Pour plus d'informations sur Web Worker, lisez la documentation ou ce didacticiel .
Voici un exemple simple avec 3 threads Web Worker qui comptent jusqu'à MAX_VALUE et montrent la valeur calculée actuelle dans notre page:
//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>
Nous pouvons voir que les trois threads sont exécutés en simultanéité et affichent leur valeur actuelle dans la page. Ils ne gèlent pas la page car ils sont exécutés en arrière-plan avec des threads séparés.
Une autre façon d'y parvenir est d'utiliser plusieurs iframes , chacun exécutant un thread. Nous pouvons donner à l' iframe des paramètres par l'URL et l' iframe peut communiquer avec son parent afin d'obtenir le résultat et de le réimprimer (l' iframe doit être dans le même domaine).
Cet exemple ne fonctionne pas dans tous les navigateurs! Les iframes fonctionnent généralement dans le même thread / processus que la page principale (mais Firefox et Chromium semblent le gérer différemment).
Étant donné que l'extrait de code ne prend pas en charge plusieurs fichiers HTML, je vais simplement fournir les différents codes ici:
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);
}
})();
La manière `` naïve '' serait d'exécuter la fonction l' setTimeout()
une après l'autre comme ceci:
setTimeout(function(){ /* Some tasks */ }, 0);
setTimeout(function(){ /* Some tasks */ }, 0);
[...]
Mais cette méthode ne fonctionne pas car chaque tâche sera exécutée l'une après l'autre.
Nous pouvons simuler une exécution asynchrone en appelant la fonction de manière récursive comme ceci:
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>
Comme vous pouvez le voir, cette deuxième méthode est très lente et fige le navigateur car elle utilise le thread principal pour exécuter les fonctions.
Le rendement est une nouvelle fonctionnalité d' ECMAScript 6 , elle ne fonctionne que sur la version la plus ancienne de Firefox et Chrome (dans Chrome, vous devez activer le JavaScript expérimental apparaissant dans chrome: // flags / # enable-javascript-harmonie ).
Le mot-clé yield provoque la pause de l'exécution de la fonction de générateur et la valeur de l'expression qui suit le mot-clé yield est renvoyée à l'appelant du générateur. Il peut être considéré comme une version basée sur un générateur du mot-clé return.
Un générateur vous permet de suspendre l'exécution d'une fonction et de la reprendre plus tard. Un générateur peut être utilisé pour planifier vos fonctions avec une technique appelée trampoline .
Voici l'exemple:
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>
Avec les "side-specs" HTML5, plus besoin de pirater javascript avec setTimeout (), setInterval (), etc.
HTML5 & Friends présente la spécification javascript Web Workers . C'est une API pour exécuter des scripts de manière asynchrone et indépendante.
Liens vers la spécification et un tutoriel .
Il n'y a pas de véritable threading en JavaScript. JavaScript étant le langage malléable qu'il est, vous permet d'en émuler une partie. Voici un exemple que j'ai rencontré l'autre jour.
Voici juste un moyen de simuler le multi-threading en Javascript
Maintenant, je vais créer 3 threads qui calculeront l'addition des nombres, les nombres peuvent être divisés par 13 et les nombres peuvent être divisés par 3 jusqu'à 10000000000. Et ces 3 fonctions ne peuvent pas fonctionner en même temps que ce que signifie la concurrence. Mais je vais vous montrer une astuce qui fera exécuter ces fonctions de manière récursive dans le même temps: jsFiddle
Ce code m'appartient.
Partie du corps
<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>
Partie 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();
J'espère que cette façon sera utile.
Vous pouvez utiliser Narrative JavaScript , un compilateur qui transforme votre code en machine à états, vous permettant ainsi d'émuler le threading. Il le fait en ajoutant un opérateur «céder» (noté «->») au langage qui vous permet d'écrire du code asynchrone dans un seul bloc de code linéaire.
Le nouveau moteur v8 qui devrait sortir aujourd'hui le soutient (je pense)
En Javascript brut, le mieux que vous puissiez faire est d'utiliser les quelques appels asynchrones (xmlhttprequest), mais ce n'est pas vraiment du threading et très limité. Google Gears ajoute un certain nombre d'API au navigateur, dont certaines peuvent être utilisées pour la prise en charge des threads.
Si vous ne pouvez ou ne voulez pas utiliser de trucs AJAX, utilisez un iframe ou dix! ;) Vous pouvez exécuter des processus dans des iframes en parallèle avec la page maître sans vous soucier des problèmes comparables entre navigateurs ou des problèmes de syntaxe avec dot net AJAX, etc., et vous pouvez appeler le JavaScript de la page maître (y compris le JavaScript qu'elle a importé) à partir d'un iframe.
Par exemple, dans un iframe parent, pour appeler egFunction()
le document parent une fois le contenu iframe chargé (c'est la partie asynchrone)
parent.egFunction();
Générez également dynamiquement les iframes pour que le code html principal en soit libre si vous le souhaitez.
Une autre méthode possible consiste à utiliser un interpréteur javascript dans l'environnement javascript.
En créant plusieurs interpréteurs et en contrôlant leur exécution à partir du thread principal, vous pouvez simuler le multi-threading avec chaque thread s'exécutant dans son propre environnement.
L'approche est quelque peu similaire à celle des travailleurs Web, mais vous donnez à l'interpréteur l'accès à l'environnement global du navigateur.
J'ai fait un petit projet pour le démontrer .
Une explication plus détaillée dans ce billet de blog .
Javascript n'a pas de threads, mais nous avons des workers.
Les ouvriers peuvent être un bon choix si vous n'avez pas besoin d'objets partagés.
La plupart des implémentations de navigateur répartiront en fait les travailleurs sur tous les cœurs, ce qui vous permettra d'utiliser tous les cœurs. Vous pouvez voir une démo de ceci ici .
J'ai développé une bibliothèque appelée task.js qui rend cela très facile à faire.
task.js Interface simplifiée pour exécuter du code gourmand en ressources processeur sur tous les cœurs (node.js et web)
Un exemple serait
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
});
Avec la spécification HTML5, vous n'avez pas besoin d'écrire trop de JS pour le même ou de trouver des hacks.
L'une des fonctionnalités introduites dans HTML5 est Web Workers, qui exécute JavaScript en arrière-plan, indépendamment des autres scripts, sans affecter les performances de la page.
Il est pris en charge dans presque tous les navigateurs:
Chrome - 4.0+
IE - 10.0+
Mozilla - 3.5+
Safari - 4.0+
Opera - 11.5+