É garantido que o JavaScript seja de thread único?

611

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?

Egor Pavlikhin
fonte
25
No contexto dos navegadores, provavelmente. Mas alguns programas permitem tratar JS como um idioma de nível superior e fornecer ligações para outras bibliotecas C ++. Por exemplo, o flusspferd (ligações C ++ para JS - AWESOME BTW) estava fazendo algumas coisas com JS multithread. Cabe ao contexto.
NG.
11
Esta é uma leitura obrigatória: developer.mozilla.org/en/docs/Web/JavaScript/EventLoop
RickyA

Respostas:

583

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 outtodos, 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.onchangeouvinte 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, changee 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 onclickmanipulador 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 addEventListenere 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 alerte 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 outna á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 showModalDialognos 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 setIntervalfunção. Se você estiver escrevendo uma estrutura que pretende ser usada por aplicativos complexos, isso pode ser uma boa jogada. postMessageesperançosamente também aliviará a dor da criação de scripts entre documentos no futuro.

bobince
fonte
14
@JP: Pessoalmente, não quero saber imediatamente, porque isso significa que tenho que ter cuidado para que meu código seja reentrante, que chamar meu código de desfoque não afetará o estado em que algum código externo depende. Existem muitos casos em que o desfoque é um efeito colateral inesperado para necessariamente capturar todos. E, infelizmente, mesmo se você fazer o quer, não é de confiança! O IE é acionado blur após o código retornar o controle para o navegador.
quer
107
Javascript é único thread. Interromper sua execução em alert () não significa que o encadeamento de eventos pare de bombear eventos. Significa apenas que seu script está dormindo enquanto o alerta está na tela, mas ele deve continuar bombeando eventos para desenhar a tela. Enquanto um alerta está ativo, a bomba de eventos está em execução, o que significa que é totalmente correto continuar enviando eventos. Na melhor das hipóteses, isso demonstra um encadeamento cooperativo que pode acontecer em javascript, mas todo esse comportamento pode ser explicado por uma função simplesmente anexando um evento à bomba de eventos para ser processado posteriormente e em execução.
Chubbsondubs
34
Mas lembre-se de que o encadeamento cooperativo ainda é de rosca única. Duas coisas não podem acontecer simultaneamente: é o que o multi-threading permite e injeta não-determinismo. Tudo o que foi descrito é determinístico e é um bom lembrete sobre esses tipos de problemas. Bom trabalho na análise @bobince
chubbsondubs
94
Chubbard está certo: o JavaScript é único. Este não é um exemplo de multithreading, mas envio de mensagem síncrona em um único encadeamento. Sim, é possível pausar a pilha e fazer com que o envio de eventos continue (por exemplo, alert ()), mas os tipos de problemas de acesso que ocorrem em ambientes multithread verdadeiros simplesmente não podem acontecer; por exemplo, você nunca terá valores de alteração variáveis ​​entre um teste e uma atribuição imediatamente subsequente, porque seu encadeamento não pode ser arbitrariamente interrompido. Receio que esta resposta só cause confusão.
Kris Giesing
19
Sim, mas como uma função de bloqueio que aguarda a entrada do usuário pode ocorrer entre duas instruções, você tem todos os problemas de consistência que os threads no nível do sistema operacional trazem para você. Se o mecanismo JavaScript realmente é executado em vários threads do SO é de pouca relevância.
22912
115

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.)

Már Örlygsson
fonte
21
Concordo. Se alguma vez o multi-threading for adicionado ao Javascript no navegador, será por meio de uma API explícita (por exemplo, Web Workers), assim como ocorre com todas as linguagens imperativas. Essa é a única maneira que faz sentido.
Dean Harding
1
Observe que existe um único. principal thread JS, mas algumas coisas são executadas no navegador em paralelo. Não é apenas uma impressão de multiencadeamento. Os pedidos são realmente executados em paralelo. Os ouvintes que você define em JS são executados um a um, mas os pedidos são verdadeiramente paralelos.
Nux
16

Sim, embora você ainda possa sofrer alguns problemas de programação simultânea (principalmente condições de corrida) ao usar qualquer uma das APIs assíncronas, como retornos de chamada setInterval e xmlhttp.

gastador
fonte
10

Sim, embora o Internet Explorer 9 compile seu Javascript em um thread separado, em preparação para execução no thread principal. Isso não muda nada para você como programador.

ChessWhiz
fonte
8

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:

    1. É tudo js código executado em um único segmento ? : Sim
    1. O código js pode fazer com que outros threads sejam executados ? : Sim
    1. Esses encadeamentos podem alterar o contexto de execução js ?: Não. Mas eles podem (direta / indiretamente (?)) Anexar à fila de eventos da qual os ouvintes podem alterar o contexto de execução . Mas não se deixe enganar, os ouvintes executam atomicamente no thread principal novamente.

Portanto, no caso de navegadores e nodejs (e provavelmente muitos outros mecanismos), o javascript não é multithread, mas os próprios mecanismos .


Atualização sobre os trabalhadores da Web:

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:

Sobre a segurança da linha :

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.


PS

Além da teoria, esteja sempre preparado sobre possíveis casos de canto e bugs descritos na resposta aceita

Marinos An
fonte
7

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. setTimeoute setIntervalsã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).

Prumo
fonte
7

Na verdade, uma janela pai pode se comunicar com janelas ou quadros filhos ou irmãos que possuem seus próprios threads de execução em execução.

Kennebec
fonte
6

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.

wle8300
fonte
4

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.

max
fonte
2
Porém, a maioria das definições de idioma é projetada para ser efetivamente segmentada por um único thread, e declara que o multithreading é permitido desde que o efeito seja idêntico. (por exemplo, UML)
jevon
1
Eu tenho que concordar com a resposta simplesmente porque o atual ECMAScript não faz nenhuma provisão (embora possa ser pensado o mesmo para C) para contextos de execução simultâneos do ECMAScript . Então, como esta resposta, eu argumentaria que qualquer implementação que possua threads simultâneos capazes de modificar um estado compartilhado é uma extensão ECMAScript .
user2864740
3

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.

Francisco Soto
fonte
2

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.

Anton Alexandrenok
fonte
-4

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).

James
fonte
5
cromo faz isso da maneira certa, não sei onde @ James vê-lo sendo multithreaded ...: 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)
Tor Valamo