Sabe-se que o JavaScript possui um único encadeamento em todas as implementações modernas de navegadores, mas isso é especificado em algum padrão ou é apenas por tradição? É totalmente seguro assumir que o JavaScript é sempre de thread único?
Sabe-se que o JavaScript possui um único encadeamento em todas as implementações modernas de navegadores, mas isso é especificado em algum padrão ou é apenas por tradição? É totalmente seguro assumir que o JavaScript é sempre de thread único?
Respostas:
Esta é uma boa pergunta. Eu adoraria dizer "sim". Não posso.
Geralmente, o JavaScript é considerado como tendo um único encadeamento de execução visível para scripts (*), para que, quando seu script embutido, ouvinte de evento ou tempo limite for digitado, você permaneça completamente no controle até retornar do final de seu bloco ou função.
(*: ignorando a questão de saber se os navegadores realmente implementam seus mecanismos JS usando um thread do SO ou se outros threads de execução limitados são introduzidos pelos WebWorkers.)
No entanto, na realidade, isso não é bem verdade , de maneiras desagradáveis.
O caso mais comum são eventos imediatos. Os navegadores os acionam imediatamente quando seu código faz algo para causar-lhes:
var l= document.getElementById('log');
var i= document.getElementById('inp');
i.onblur= function() {
l.value+= 'blur\n';
};
setTimeout(function() {
l.value+= 'log in\n';
l.focus();
l.value+= 'log out\n';
}, 100);
i.focus();
<textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">
Resultados em log in, blur, log out
todos, exceto no IE. Esses eventos não são acionados apenas porque você ligou focus()
diretamente, eles podem acontecer porque você ligou alert()
ou abriu uma janela pop-up ou qualquer outra coisa que mova o foco.
Isso também pode resultar em outros eventos. Por exemplo, adicione um i.onchange
ouvinte e digite algo na entrada antes que a focus()
chamada o desvie, e a ordem do log será log in, change, blur, log out
, exceto no Opera, onde está log in, blur, log out, change
e no IE, onde está (ainda menos explicitamente) log in, change, log out, blur
.
Da mesma forma, chamar click()
um elemento que o fornece chama o onclick
manipulador imediatamente em todos os navegadores (pelo menos isso é consistente!).
(Estou usando as on...
propriedades do manipulador de eventos diretos aqui, mas o mesmo acontece com addEventListener
e attachEvent
.)
Também existem várias circunstâncias em que os eventos podem ser disparados enquanto o código é encadeado, apesar de você não ter feito nada para provocá-lo. Um exemplo:
var l= document.getElementById('log');
document.getElementById('act').onclick= function() {
l.value+= 'alert in\n';
alert('alert!');
l.value+= 'alert out\n';
};
window.onresize= function() {
l.value+= 'resize\n';
};
<textarea id="log" rows="20" cols="40"></textarea>
<button id="act">alert</button>
Pressione alert
e você receberá uma caixa de diálogo modal. Não é mais necessário executar o script até você descartar esse diálogo, sim? Não. Redimensione a janela principal e você entrará alert in, resize, alert out
na área de texto.
Você pode pensar que é impossível redimensionar uma janela enquanto uma caixa de diálogo modal estiver aberta, mas não é assim: no Linux, você pode redimensionar a janela o quanto quiser; no Windows, não é tão fácil, mas você pode fazer isso alterando a resolução da tela de maior para menor, onde a janela não se encaixa, fazendo com que ela seja redimensionada.
Você pode pensar, bem, é apenas resize
(e provavelmente mais um pouco scroll
) que pode ser acionado quando o usuário não tem interação ativa com o navegador porque o script é encadeado. E para janelas únicas, você pode estar certo. Mas tudo isso vai para o pote assim que você cria scripts entre janelas. Para todos os navegadores que não sejam o Safari, que bloqueiam todas as janelas / guias / quadros quando qualquer um deles está ocupado, você pode interagir com um documento a partir do código de outro documento, executando em um encadeamento de execução separado e fazendo com que qualquer manipulador de eventos relacionado fogo.
Locais onde os eventos que você pode causar a geração podem ser gerados enquanto o script ainda está em thread:
quando os popups modais ( alert
, confirm
, prompt
) estão abertas, em todos os navegadores, mas Opera;
durante showModalDialog
nos navegadores que o suportam;
a caixa de diálogo "Um script nesta página pode estar ocupado ...", mesmo se você optar por deixar o script continuar em execução, permite que eventos como redimensionar e desfocar sejam acionados e sejam tratados mesmo quando o script estiver no meio de um ocupado, exceto no Opera.
Há algum tempo, para mim, no IE com o Sun Java Plugin, chamar qualquer método em um applet poderia permitir que os eventos disparassem e o script fosse reinserido. Esse sempre foi um bug sensível ao tempo, e é possível que a Sun o tenha corrigido desde então (espero que sim).
provavelmente mais. Já faz um tempo desde que eu testei isso e os navegadores ganharam complexidade desde então.
Em resumo, o JavaScript parece para a maioria dos usuários, na maioria das vezes, ter um único encadeamento de execução estrito e controlado por eventos. Na realidade, não existe. Não está claro quanto disso é simplesmente um bug e qual é o design deliberado, mas se você estiver escrevendo aplicativos complexos, especialmente os de janela cruzada / scripts de quadros, há todas as chances de que isso o atinja - e de forma intermitente, maneiras difíceis de depurar.
Se o pior acontecer, você pode resolver problemas de simultaneidade indiretamente todas as respostas do evento. Quando um evento chegar, solte-o em uma fila e lide com a fila em ordem posterior, em uma setInterval
função. Se você estiver escrevendo uma estrutura que pretende ser usada por aplicativos complexos, isso pode ser uma boa jogada. postMessage
esperançosamente também aliviará a dor da criação de scripts entre documentos no futuro.
blur
após o código retornar o controle para o navegador.
Eu diria que sim - porque praticamente todo o código javascript existente (pelo menos não trivial) seria interrompido se o mecanismo javascript do navegador fosse executado de forma assíncrona.
Acrescente a isso o fato de que o HTML5 já especifica Trabalhadores da Web (uma API padronizada e explícita para código javascript com vários threads) que introduza vários threads no Javascript básico não faria sentido.
( Observação para outros comentaristas: mesmo assim setTimeout/setInterval
, os eventos de solicitação de carga HTTP (XHR) e os eventos da interface do usuário (clique, foco, etc.) fornecem uma impressão grosseira de multithreaded - eles ainda são todos executados em uma única linha do tempo - uma em por um tempo - portanto, mesmo que não conheçamos sua ordem de execução de antemão, não há necessidade de se preocupar com alterações nas condições externas durante a execução de um manipulador de eventos, função temporizada ou retorno de chamada XHR.)
Eu diria que a especificação não impede que alguém crie um mecanismo que execute javascript em vários threads , exigindo que o código execute a sincronização para acessar o estado do objeto compartilhado.
Eu acho que o paradigma não-bloqueador de thread único surgiu da necessidade de executar o javascript nos navegadores onde a interface do usuário nunca deve bloquear.
Nodejs seguiu a abordagem dos navegadores .
O mecanismo Rhino , no entanto, suporta a execução do código js em diferentes threads . As execuções não podem compartilhar contexto, mas podem compartilhar escopo. Para este caso específico, a documentação declara:
... "O Rhino garante que o acesso a propriedades de objetos JavaScript seja atômico entre threads, mas não garante mais a execução de scripts no mesmo escopo ao mesmo tempo. Se dois scripts usam o mesmo escopo simultaneamente, os scripts são responsável por coordenar qualquer acesso a variáveis compartilhadas ".
Ao ler a documentação do Rhino, concluo que é possível alguém escrever uma API javascript que também gera novos threads javascript, mas a API seria específica para o rinoceronte (por exemplo, o nó pode gerar um novo processo).
Eu imagino que, mesmo para um mecanismo que suporte vários threads em javascript, deve haver compatibilidade com scripts que não consideram multiencadeamento ou bloqueio.
Concearning navegadores e nodejs da maneira que eu vejo:
Portanto, no caso de navegadores e nodejs (e provavelmente muitos outros mecanismos), o javascript não é multithread, mas os próprios mecanismos .
A presença de trabalhadores da web justifica ainda mais que o javascript pode ser multiencadeado, no sentido de que alguém pode criar código em javascript que será executado em um encadeamento separado.
No entanto: os trabalhadores da Web não corrigem os problemas dos threads tradicionais que podem compartilhar o contexto de execução. As regras 2 e 3 acima ainda se aplicam , mas desta vez o código encadeado é criado pelo usuário (gravador de código js) em javascript.
A única coisa a considerar é o número de threads gerados, do ponto de vista da eficiência (e não da simultaneidade ). Ver abaixo:
A interface Worker gera threads reais no nível do sistema operacional e os programadores conscientes podem estar preocupados com o fato de a simultaneidade poder causar efeitos "interessantes" no seu código, se você não tomar cuidado.
No entanto, como os trabalhadores da Web controlam cuidadosamente os pontos de comunicação com outros threads, na verdade é muito difícil causar problemas de simultaneidade . Não há acesso a componentes não seguros para thread ou ao DOM. E você precisa passar dados específicos dentro e fora de um encadeamento através de objetos serializados. Então você tem que trabalhar muito para causar problemas no seu código.
Além da teoria, esteja sempre preparado sobre possíveis casos de canto e bugs descritos na resposta aceita
JavaScript / ECMAScript foi projetado para viver em um ambiente host. Ou seja, o JavaScript não faz nada , a menos que o ambiente host decida analisar e executar um determinado script e forneça objetos de ambiente que permitam que o JavaScript seja realmente útil (como o DOM nos navegadores).
Eu acho que uma determinada função ou bloco de script executará linha por linha e isso é garantido para JavaScript. No entanto, talvez um ambiente host possa executar vários scripts ao mesmo tempo. Ou, um ambiente host sempre pode fornecer um objeto que oferece multiencadeamento. setTimeout
e setInterval
são exemplos, ou pelo menos pseudo-exemplos, de um ambiente host que fornece uma maneira de fazer alguma simultaneidade (mesmo que não seja exatamente simultânea).
O @Bobince está fornecendo uma resposta realmente opaca.
Partindo da resposta de Már Örlygsson, o Javascript é sempre de thread único por causa deste simples fato: tudo em Javascript é executado em uma única linha do tempo.
Essa é a definição estrita de uma linguagem de programação de thread único.
Não.
Estou indo contra a multidão aqui, mas tenha paciência comigo. Um único script JS deve ser efetivamente único encadeado, mas isso não significa que não possa ser interpretado de maneira diferente.
Digamos que você tenha o seguinte código ...
var list = [];
for (var i = 0; i < 10000; i++) {
list[i] = i * i;
}
Isso está escrito com a expectativa de que, no final do loop, a lista deva ter 10000 entradas que são o índice ao quadrado, mas a VM poderá perceber que cada iteração do loop não afeta a outra e reinterpretar usando dois threads.
Primeira discussão
for (var i = 0; i < 5000; i++) {
list[i] = i * i;
}
Segunda discussão
for (var i = 5000; i < 10000; i++) {
list[i] = i * i;
}
Estou simplificando aqui, porque matrizes JS são mais complicadas do que pedaços de memória idiotas, mas se esses dois scripts puderem adicionar entradas à matriz de uma maneira segura para threads, quando a execução de ambos terminar, haverá o mesmo resultado que a versão single-threaded.
Embora eu não esteja ciente de qualquer VM detectando código paralelizável como este, parece provável que ele possa existir no futuro para VMs JIT, pois pode oferecer mais velocidade em algumas situações.
Levando esse conceito adiante, é possível que o código possa ser anotado para que a VM saiba o que converter em código multiencadeado.
// like "use strict" this enables certain features on compatible VMs.
"use parallel";
var list = [];
// This string, which has no effect on incompatible VMs, enables threading on
// this loop.
"parallel for";
for (var i = 0; i < 10000; i++) {
list[i] = i * i;
}
Como os Web Workers estão adotando o Javascript, é improvável que esse sistema mais feio venha a existir, mas acho seguro dizer que o Javascript é único por tradição.
Bem, o Chrome é multiprocesso, e acho que todo processo lida com seu próprio código Javascript, mas, tanto quanto o código sabe, é "single-threaded".
Não há suporte algum em Javascript para multiencadeamento, pelo menos não explicitamente, portanto isso não faz diferença.
Eu tentei o exemplo do @ bobince com algumas modificações:
<html>
<head>
<title>Test</title>
</head>
<body>
<textarea id="log" rows="20" cols="40"></textarea>
<br />
<button id="act">Run</button>
<script type="text/javascript">
let l= document.getElementById('log');
let b = document.getElementById('act');
let s = 0;
b.addEventListener('click', function() {
l.value += 'click begin\n';
s = 10;
let s2 = s;
alert('alert!');
s = s + s2;
l.value += 'click end\n';
l.value += `result = ${s}, should be ${s2 + s2}\n`;
l.value += '----------\n';
});
window.addEventListener('resize', function() {
if (s === 10) {
s = 5;
}
l.value+= 'resize\n';
});
</script>
</body>
</html>
Portanto, ao pressionar Executar, feche o pop-up de alerta e faça um "thread único", você verá algo assim:
click begin
click end
result = 20, should be 20
Mas se você tentar executar isso no Opera ou Firefox estável no Windows e minimizar / maximizar a janela com alerta pop-up na tela, haverá algo como isto:
click begin
resize
click end
result = 15, should be 20
Não quero dizer que isso é "multithreading", mas algum código foi executado em um momento errado comigo, sem esperar por isso, e agora estou com um estado corrompido. E melhor saber sobre esse comportamento.
Tente aninhar duas funções setTimeout uma na outra e elas se comportarão com vários segmentos (ou seja, o timer externo não esperará que o interno seja concluído antes de executar sua função).
setTimeout(function(){setTimeout(function(){console.log('i herd you liek async')}, 0); alert('yo dawg!')}, 0)
(para o registro, yo dawg deve vir sempre em primeiro lugar, então a saída do log console)