JavaScript et threads

137

Existe-t-il un moyen de faire du multi-threading en JavaScript?

Niyaz
la source
2
JavaScript ne contient pas de threads, du moins dans sa forme actuelle. Qu'est-ce que vous essayez de faire exactement?
Eric Pohl
3
Pouvez-vous changer la réponse acceptée par celle-ci? stackoverflow.com/a/30891727/2576706 Il est beaucoup plus développé pour les futurs utilisateurs ..
Ludovic Feltz

Réponses:

109

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é):

  • Démo de Google Gears WorkerPool (ce n'est pas un bon exemple car il s'exécute trop vite pour être testé dans Chrome et Firefox, bien qu'IE l'exécute assez lentement pour le voir bloquer l'interaction)

IE8 et IE9 ne peuvent faire des threads qu'avec le plugin Gears installé

Sam Hasler
la source
1
Bien que Safari 4 prenne en charge les web workers, il semble que seul Firefox supporte le passage d'objets complexes via postMessage: hacks.mozilla.org/2009/07/working-smarter-not-harder Voir le dernier paragraphe de cet article sur l'utilisation du monde réel dans le projet Bespin pour des liens vers un shim pour qui implémente l'API Worker en termes de Google Gears et qui ajoute les fonctionnalités manquantes à l'implémentation worker de Safari 4 et des détails sur la façon dont ils ont implémenté des événements personnalisés transparents au-dessus de l'interface postMessage.
Sam Hasler le
6
Maintenant que IE9 est sorti, vous pouvez mettre à jour "IE8 ne peut faire des threads avec le plugin Gears installé" vers "IE8 et IE9 ne peut faire des threads qu'avec le plugin Gears installé"
BenoitParis
2
@ inf3rno pour avoir effectué de longs calculs sur un autre thread afin de ne pas ralentir l'interface utilisateur du navigateur.
Sam Hasler
6
@SamHasler Vous voudrez peut-être réviser votre réponse. Les agents Web sont désormais pris en charge par tous les navigateurs de bureau modernes. Voir aussi caniuse.com/#search=worker
Rob W
2
@SamHasler, il convient également de noter que Google Gears n'est plus pris en charge.
skeggse
73

Différentes façons de faire du multi-threading et asynchrone en JavaScript

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(), XMLHttpRequestou 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.


Véritable multi-threading

Multi-threading: threads de travail JavaScript

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.


Multi-threading: avec plusieurs iframes

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);
    }
})();

Simuler le multi-threading

Single-thread: émuler la concurrence JavaScript avec setTimeout ()

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.


Single-thread: émuler la concurrence JavaScript avec rendement

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>

Ludovic Feltz
la source
3
Cela devrait vraiment être la meilleure réponse.
Jerry Liu
14

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 .

Djondal
la source
11

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.

Svend
la source
1
Qu'entendez-vous par "vrai filetage"? Les fils verts sont de vrais fils.
Wes
10

Il n'y a pas de véritable multi-threading en Javascript, mais vous pouvez obtenir un comportement asynchrone en utilisant setTimeout()des requêtes AJAX asynchrones.

Qu'essayez-vous d'accomplir exactement?

17 sur 26
la source
7

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.

Ahmet Can Güven
la source
6

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.

Antoine Aubry
la source
4

Le nouveau moteur v8 qui devrait sortir aujourd'hui le soutient (je pense)

Juan
la source
3

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.

jsight
la source
1
L'API Google Gears n'est plus disponible.
Ludovic Feltz
3

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.

Jono
la source
1
Cette description était un peu trop brève à mon goût. Pourriez-vous expliquer comment utiliser cette technique ou publier un lien vers un didacticiel montrant du code?
oligofren
3

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 .

Amitay Dobo
la source
1

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
});
Chad Scira
la source
0

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+

Mandeep Singh
la source