JavaScript e Threads

137

Existe alguma maneira de executar multi-threading em JavaScript?

Niyaz
fonte
2
JavaScript não contém threads, pelo menos em sua forma atual. O que exatamente você está tentando fazer?
Eric Pohl
3
Você pode alterar a resposta aceita para esta? stackoverflow.com/a/30891727/2576706 é muito mais desenvolvido para o usuário futuro ..
Ludovic Feltz

Respostas:

109

Consulte http://caniuse.com/#search=worker para obter as informações de suporte mais atualizadas.

O seguinte foi o estado de apoio por volta de 2009.


As palavras para as quais você deseja pesquisar no Google são Threads de trabalho do JavaScript

Além do Gears, não há nada disponível no momento, mas há muita conversa sobre como implementar isso, então acho que assisto a essa pergunta, pois a resposta sem dúvida mudará no futuro.

Aqui está a documentação relevante para a API Gears: WorkerPool

O WHATWG possui um rascunho de recomendação para threads de trabalho: Trabalhadores da Web

E há também os threads de trabalho DOM da Mozilla


Atualização: junho de 2009, estado atual do suporte ao navegador para threads JavaScript

O Firefox 3.5 possui trabalhadores da web. Algumas demos dos trabalhadores da Web, se você quiser vê-los em ação:

O plug-in Gears também pode ser instalado no Firefox.

O Safari 4 e as diárias do WebKit têm threads de trabalho:

O Chrome tem o Gears instalado, para que ele possa executar threads, embora exija uma solicitação de confirmação do usuário (e use uma API diferente para os trabalhadores da Web, embora funcione em qualquer navegador com o plug-in Gears instalado):

O IE8 e o IE9 só podem fazer threads com o plug-in Gears instalado

Sam Hasler
fonte
1
Embora o Safari 4 ofereça suporte a profissionais da Web, parece que apenas o Firefox suporta a passagem de objetos complexos via postMessage: hacks.mozilla.org/2009/07/working-smarter-not-harder Veja o último parágrafo dessa publicação sobre uso no mundo real no projeto Bespin para obter links para um shim que implementa a API Worker em termos do Google Gears e que adiciona os recursos ausentes à implementação do worker do Safari 4 e detalhes de como eles implementaram eventos personalizados transparentes na parte superior da interface postMessage.
9339 Sam Hasler
6
Agora que o IE9 saiu, você pode atualizar "O IE8 só pode executar threads com o plug-in Gears instalado" para "IE8 e IE9 só pode
executar
2
@ inf3rno por fazer cálculos demorados em outro thread, para que não diminua a velocidade da interface do navegador.
Sam Hasler
6
@ SamHasler Você pode revisar sua resposta. Os trabalhadores da Web agora são suportados por todos os navegadores de desktop modernos. Veja também caniuse.com/#search=worker
Rob W
2
@SamHasler também vale a pena notar que o Google Gears não é mais suportado.
Skeggse #
73

Maneira diferente de executar multiencadeamento e assíncrono em JavaScript

Antes do HTML5, o JavaScript permitia apenas a execução de um thread por página.

Houve alguma forma hacky para simular uma execução assíncrono com Rendimento , setTimeout(), setInterval(), XMLHttpRequestou manipuladores de eventos (ver o fim deste post para um exemplo com rendimento e setTimeout()).

Mas com o HTML5, agora podemos usar Threads de Trabalho para paralelizar a execução de funções. Aqui está um exemplo de uso.


Multi-threading real

Multiencadeamento: Threads de trabalho do JavaScript

O HTML5 introduziu threads de trabalho da Web (consulte: compatibilidade de navegadores )
Nota: O IE9 e versões anteriores não o suportam.

Esses threads de trabalho são threads JavaScript executados em segundo plano sem afetar o desempenho da página. Para obter mais informações sobre o Web Worker, leia a documentação ou este tutorial .

Aqui está um exemplo simples com três threads de trabalho da Web que contam até MAX_VALUE e mostram o valor calculado atual em nossa 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 os três threads são executados em simultâneo e imprimem seu valor atual na página. Eles não congelam a página porque são executados em segundo plano com threads separados.


Multiencadeamento: com vários iframes

Outra maneira de conseguir isso é usar vários iframes , cada um executando um encadeamento. Podemos fornecer ao iframe alguns parâmetros pelo URL e o iframe pode se comunicar com o pai ou mãe para obter o resultado e imprimi-lo novamente (o iframe deve estar no mesmo domínio).

Este exemplo não funciona em todos os navegadores! Os iframes geralmente são executados no mesmo encadeamento / processo da página principal (mas o Firefox e o Chromium parecem lidar com isso de maneira diferente).

Como o snippet de código não suporta vários arquivos HTML, fornecerei apenas os diferentes códigos aqui:

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 multiencadeamento

Encadeamento único: emule a simultaneidade do JavaScript com setTimeout ()

A maneira 'ingênua' seria executar a função setTimeout()uma após a outra assim:

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

Mas esse método não funciona porque cada tarefa será executada uma após a outra.

Podemos simular a execução assíncrona chamando a função recursivamente assim:

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 você pode ver, este segundo método é muito lento e congela o navegador porque ele usa o thread principal para executar as funções.


Encadeamento único: emule a simultaneidade do JavaScript com rendimento

O rendimento é um novo recurso do ECMAScript 6 , que funciona apenas na versão mais antiga do Firefox e do Chrome (no Chrome, é necessário ativar o JavaScript experimental que aparece no chrome: // flags / # enable-javascript-harmony ).

A palavra-chave yield faz com que a execução da função do gerador seja pausada e o valor da expressão após a palavra-chave yield é retornado ao chamador do gerador. Pode ser pensado como uma versão baseada em gerador da palavra-chave return.

Um gerador permite suspender a execução de uma função e retomar mais tarde. Um gerador pode ser usado para agendar suas funções com uma técnica chamada trampolim .

Aqui está o exemplo:

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
fonte
3
Essa realmente deve ser a melhor resposta.
Jerry Liu
14

Com as "especificações laterais" do HTML5, não é mais necessário hackear o javascript com setTimeout (), setInterval () etc.

O HTML5 & Friends apresenta a especificação javascript dos Trabalhadores da Web . É uma API para executar scripts de forma assíncrona e independente.

Links para a especificação e um tutorial .

djondal
fonte
11

Não há verdadeiro encadeamento em JavaScript. O JavaScript, sendo a linguagem maleável, permite simular parte dela. Aqui está um exemplo que me deparei no outro dia.

Svend
fonte
1
O que você quer dizer com "verdadeiro encadeamento"? Linhas verdes são linhas verdadeiras.
21413 Wes Wes
10

Não há verdadeiro multiencadeamento no Javascript, mas você pode obter um comportamento assíncrono usando setTimeout()solicitações AJAX assíncronas.

O que exatamente você está tentando realizar?

17 de 26
fonte
7

Aqui está apenas uma maneira de simular multi-threading em Javascript

Agora vou criar 3 threads que calcularão a adição de números, os números podem ser divididos com 13 e os números podem ser divididos com 3 até 10000000000. E essas 3 funções não podem ser executadas ao mesmo tempo que o que significa simultaneidade. Mas vou mostrar um truque que fará com que essas funções sejam executadas recursivamente ao mesmo tempo: jsFiddle

Este código pertence a mim.

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

Espero que assim seja útil.

Ahmet Can Güven
fonte
6

Você pode usar o Narrative JavaScript , um compilador que transforma seu código em uma máquina de estado, permitindo efetivamente emular a segmentação. Isso é feito adicionando um operador "produtivo" (anotado como '->') ao idioma que permite escrever código assíncrono em um único bloco de código linear.

Antoine Aubry
fonte
4

O novo motor v8 que deve sair hoje o suporta (eu acho)

juan
fonte
3

No Javascript bruto, o melhor que você pode fazer é usar as poucas chamadas assíncronas (xmlhttprequest), mas isso não é realmente de encadeamento e muito limitado. O Google Gears adiciona várias APIs ao navegador, algumas das quais podem ser usadas para suporte de segmentação.

jsight
fonte
1
A API do Google Gears não está mais disponível.
Ludovic Feltz
3

Se você não pode ou não deseja usar nada do AJAX, use um iframe ou dez! ;) Você pode ter processos em execução em iframes em paralelo à página mestre sem se preocupar com problemas comparáveis ​​entre navegadores ou problemas de sintaxe com o dot net AJAX etc., e pode chamar o JavaScript da página mestre (incluindo o JavaScript que foi importado) de um iframe.

Por exemplo, em um iframe pai, para chamar egFunction()o documento pai depois que o conteúdo do iframe for carregado (essa é a parte assíncrona)

parent.egFunction();

Gere dinamicamente os iframes também, para que o código html principal fique livre deles, se desejar.

Jono
fonte
1
Esta descrição foi um pouco breve para o meu gosto. Você poderia elaborar como fazer essa técnica ou postar algum link em um tutorial mostrando algum código?
Oligofren
3

Outro método possível é usar um interpretador javascript no ambiente javascript.

Criando vários intérpretes e controlando sua execução a partir do encadeamento principal, você pode simular multiencadeamento com cada encadeamento em execução em seu próprio ambiente.

A abordagem é um pouco semelhante aos trabalhadores da Web, mas você concede ao intérprete acesso ao ambiente global do navegador.

Eu fiz um pequeno projeto para demonstrar isso .

Uma explicação mais detalhada nesta postagem do blog .

Amitay Dobo
fonte
1

Javascript não tem threads, mas temos trabalhadores.

Os trabalhadores podem ser uma boa opção se você não precisar de objetos compartilhados.

A maioria das implementações de navegadores realmente espalhará trabalhadores por todos os núcleos, permitindo que você utilize todos os núcleos. Você pode ver uma demonstração disso aqui .

Eu desenvolvi uma biblioteca chamada task.js que facilita muito isso.

task.js Interface simplificada para que o código intensivo da CPU seja executado em todos os núcleos (node.js e web)

Um exemplo seria

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

Com a especificação HTML5, você não precisa escrever muito JS para o mesmo nem encontrar alguns hacks.

Um dos recursos introduzidos no HTML5 é o Web Workers, que é JavaScript em execução em segundo plano, independentemente de outros scripts, sem afetar o desempenho da página.

É suportado em quase todos os navegadores:

Chrome - 4.0 ou superior

IE - 10.0+

Mozilla - 3.5+

Safari - 4.0 ou superior

Opera - 11,5+

Mandeep Singh
fonte