JavaScript y subprocesos

137

¿Hay alguna manera de hacer subprocesos múltiples en JavaScript?

Niyaz
fuente
2
JavaScript no contiene hilos, al menos en su forma actual. ¿Qué estás tratando de hacer exactamente?
Eric Pohl el
3
¿Puedes cambiar la respuesta aceptada a esta? stackoverflow.com/a/30891727/2576706 Está mucho más desarrollado para futuros usuarios ...
Ludovic Feltz

Respuestas:

109

Consulte http://caniuse.com/#search=worker para obtener la información de soporte más actualizada.

El siguiente fue el estado de apoyo alrededor de 2009.


Las palabras que desea buscar en google son JavaScript Worker Threads

Además de Gears, no hay nada disponible en este momento, pero se habla mucho sobre cómo implementar esto, así que supongo que observe esta pregunta, ya que la respuesta sin duda cambiará en el futuro.

Aquí está la documentación relevante para Gears: API WorkerPool

WHATWG tiene un borrador de recomendación para hilos de trabajo: Web Workers

Y también hay hilos de trabajo DOM de Mozilla


Actualización: junio de 2009, estado actual del soporte del navegador para hilos JavaScript

Firefox 3.5 tiene trabajadores web. Algunas demostraciones de trabajadores web, si quieres verlos en acción:

El complemento Gears también se puede instalar en Firefox.

Safari 4 y los nightlies de WebKit tienen hilos de trabajo:

Chrome tiene Gears incorporados, por lo que puede hacer hilos, aunque requiere una confirmación del usuario (y utiliza una API diferente para los trabajadores web, aunque funcionará en cualquier navegador con el complemento Gears instalado):

  • Demostración de Google Gears WorkerPool (no es un buen ejemplo, ya que se ejecuta demasiado rápido para probar en Chrome y Firefox, aunque IE lo ejecuta lo suficientemente lento como para ver que bloquea la interacción)

IE8 e IE9 solo pueden hacer hilos con el complemento Gears instalado

Sam Hasler
fuente
1
Aunque Safari 4 es compatible con los trabajadores web, parece que solo Firefox es compatible con el paso de objetos complejos a través de postMessage: hacks.mozilla.org/2009/07/working-smarter-not-harder Vea el último párrafo de esa publicación sobre el uso en el mundo real en el proyecto Bespin para enlaces a una cuña que implementa la API del trabajador en términos de Google Gears y que agrega las características faltantes a la implementación del trabajador de Safari 4 y detalles de cómo implementaron eventos personalizados transparentes en la parte superior de la interfaz postMessage.
Sam Hasler
66
Ahora que IE9 está fuera, puede actualizar "IE8 solo puede hacer hilos con el complemento Gears instalado" a "IE8 e IE9 solo puede hacer hilos con el complemento Gears instalado"
BenoitParis
2
@ inf3rno por hacer largos cálculos en otro hilo para que no ralenticen la interfaz de usuario del navegador.
Sam Hasler
66
@SamHasler Es posible que desee revisar su respuesta. Los trabajadores web ahora son compatibles con todos los navegadores de escritorio modernos. Ver también caniuse.com/#search=worker
Rob W
2
@SamHasler también vale la pena señalar que Google Gears ya no es compatible.
skeggse
73

Manera diferente de hacer subprocesos múltiples y asíncrono en JavaScript

Antes de HTML5, JavaScript solo permitía la ejecución de un hilo por página.

Hubo alguna manera hacky para simular una ejecución asíncrona con Rendimiento , setTimeout(), setInterval(), XMLHttpRequesto controladores de eventos (véase el final de este post para un ejemplo con rendimiento y setTimeout()).

Pero con HTML5 ahora podemos usar Worker Threads para paralelizar la ejecución de funciones. Aquí hay un ejemplo de uso.


Real multihilo

Multi-threading: JavaScript Worker Threads

HTML5 introdujo Web Worker Threads (ver: compatibilidad de navegadores )
Nota: IE9 y versiones anteriores no lo admiten.

Estos hilos de trabajo son hilos de JavaScript que se ejecutan en segundo plano sin afectar el rendimiento de la página. Para obtener más información sobre Web Worker, lea la documentación o este tutorial .

Aquí hay un ejemplo simple con 3 hilos de Web Worker que cuentan hasta MAX_VALUE y muestran el valor calculado actual en nuestra página:

//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>

Podemos ver que los tres hilos se ejecutan en concurrencia e imprimen su valor actual en la página. No congelan la página porque se ejecutan en segundo plano con hilos separados.


Multihilo: con múltiples iframes

Otra forma de lograr esto es usar múltiples iframes , cada uno ejecutará un hilo. Podemos proporcionarle al iframe algunos parámetros mediante la URL y el iframe puede comunicarse con su padre para obtener el resultado e imprimirlo de nuevo (el iframe debe estar en el mismo dominio).

¡Este ejemplo no funciona en todos los navegadores! Los iframes generalmente se ejecutan en el mismo hilo / proceso que la página principal (pero Firefox y Chromium parecen manejarlo de manera diferente).

Dado que el fragmento de código no admite varios archivos HTML, solo proporcionaré los diferentes códigos aquí:

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

Simular subprocesos múltiples

Subproceso único: emule la concurrencia de JavaScript con setTimeout ()

La forma 'ingenua' sería ejecutar la función setTimeout()una tras otra de esta manera:

setTimeout(function(){ /* Some tasks */ }, 0);
setTimeout(function(){ /* Some tasks */ }, 0);
[...]

Pero este método no funciona porque cada tarea se ejecutará una tras otra.

Podemos simular la ejecución asincrónica llamando a la función de forma recursiva de esta manera:

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>

Como puede ver, este segundo método es muy lento y congela el navegador porque usa el hilo principal para ejecutar las funciones.


Subproceso único: emule la concurrencia de JavaScript con el rendimiento

El rendimiento es una nueva característica en ECMAScript 6 , solo funciona en la versión más antigua de Firefox y Chrome (en Chrome necesita habilitar JavaScript experimental que aparece en chrome: // flags / # enable-javascript-harmony ).

La palabra clave de rendimiento hace que la ejecución de la función del generador se detenga y el valor de la expresión que sigue a la palabra clave de rendimiento se devuelve al llamador del generador. Puede considerarse como una versión basada en generador de la palabra clave return.

Un generador le permite suspender la ejecución de una función y reanudarla más tarde. Se puede usar un generador para programar sus funciones con una técnica llamada trampolín .

Aquí está el ejemplo:

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
fuente
3
Esta realmente debería ser la mejor respuesta.
Jerry Liu
14

Con las "especificaciones laterales" HTML5 ya no es necesario hackear javascript con setTimeout (), setInterval (), etc.

HTML5 & Friends presenta la especificación de JavaScript Web Workers . Es una API para ejecutar scripts de forma asincrónica e independiente.

Enlaces a la especificación y un tutorial .

djondal
fuente
11

No hay hilos verdaderos en JavaScript. Siendo JavaScript el lenguaje maleable que es, le permite emular parte de él. Aquí hay un ejemplo que encontré el otro día.

Svend
fuente
1
¿Qué quieres decir con "roscado verdadero"? Los hilos verdes son hilos verdaderos.
Wes
10

No hay verdadero subprocesamiento múltiple en Javascript, pero puede obtener un comportamiento asincrónico utilizando setTimeout() solicitudes AJAX asincrónicas.

¿Qué es exactamente lo que estás tratando de lograr?

17 de 26
fuente
7

Aquí hay una manera de simular múltiples subprocesos en Javascript

Ahora voy a crear 3 hilos que calcularán la suma de números, los números se pueden dividir con 13 y los números se pueden dividir con 3 hasta 10000000000. Y estas 3 funciones no pueden ejecutarse al mismo tiempo que lo que significa Concurrencia. Pero te mostraré un truco que hará que estas funciones se ejecuten de forma recursiva al mismo tiempo: jsFiddle

Este código me pertenece

Parte del cuerpo

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

Espero que de esta manera sea útil.

Ahmet Can Güven
fuente
6

Podría usar Narrative JavaScript , un compilador que transformará su código en una máquina de estados, lo que le permitirá emular los subprocesos. Lo hace agregando un operador "productivo" (anotado como '->') al lenguaje que le permite escribir código asincrónico en un único bloque de código lineal.

Antoine Aubry
fuente
3

En Javascript sin procesar, lo mejor que puede hacer es usar las pocas llamadas asincrónicas (xmlhttprequest), pero eso no es realmente subprocesamiento y muy limitado. Google Gears agrega una serie de API al navegador, algunas de las cuales se pueden usar para la compatibilidad con subprocesos.

jsight
fuente
1
La API de Google Gears ya no está disponible.
Ludovic Feltz
3

Si no puede o no quiere usar nada de AJAX, ¡use un iframe o diez! ;) Puede tener procesos ejecutándose en iframes en paralelo con la página maestra sin preocuparse por problemas comparables entre navegadores o problemas de sintaxis con dot net AJAX, etc., y puede llamar al JavaScript de la página maestra (incluido el JavaScript que ha importado) desde un iframe

Por ejemplo, en un iframe padre, para llamar egFunction()al documento padre una vez que se ha cargado el contenido del iframe (esa es la parte asincrónica)

parent.egFunction();

Genere dinámicamente los iframes también para que el código html principal esté libre de ellos si lo desea.

Jono
fuente
1
Esta descripción fue demasiado breve para mi gusto. ¿Podrías detallar cómo hacer esta técnica o publicar algún enlace a un tutorial que muestre algún código?
oligofren
3

Otro método posible es utilizar un intérprete de JavaScript en el entorno de JavaScript.

Al crear múltiples intérpretes y controlar su ejecución desde el hilo principal, puede simular múltiples hilos con cada hilo ejecutándose en su propio entorno.

El enfoque es algo similar a los trabajadores web, pero le da al intérprete acceso al entorno global del navegador.

Hice un pequeño proyecto para demostrar esto .

Una explicación más detallada en esta publicación de blog .

Amitay Dobo
fuente
1

Javascript no tiene hilos, pero tenemos trabajadores.

Los trabajadores pueden ser una buena opción si no necesita objetos compartidos.

La mayoría de las implementaciones de navegador en realidad distribuirán trabajadores en todos los núcleos, lo que le permitirá utilizar todos los núcleos. Puedes ver una demostración de esto aquí .

He desarrollado una biblioteca llamada task.js que hace que esto sea muy fácil de hacer.

task.js Interfaz simplificada para hacer que el código intensivo de la CPU se ejecute en todos los núcleos (node.js y web)

Un ejemplo sería

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
fuente
0

Con la especificación HTML5 no necesita escribir demasiado JS para el mismo o encontrar algunos hacks.

Una de las características introducidas en HTML5 es Web Workers que es JavaScript que se ejecuta en segundo plano, independientemente de otros scripts, sin afectar el rendimiento de la página.

Es compatible con casi todos los navegadores:

Chrome - 4.0+

IE - 10.0+

Mozilla - 3.5+

Safari - 4.0+

Opera - 11.5+

Mandeep Singh
fuente