Compreendendo o loop de eventos

135

Eu estou pensando sobre isso e é isso que eu vim com:

Digamos que temos um código como este:

console.clear();
console.log("a");
setTimeout(function(){console.log("b");},1000);
console.log("c");
setTimeout(function(){console.log("d");},0);

Uma solicitação é recebida e o mecanismo JS começa a executar o código acima, passo a passo. As duas primeiras são de sincronização. Mas quando se trata de setTimeoutmétodo, ele se torna uma execução assíncrona. Mas o JS retorna imediatamente e continua executando, que é chamado Non-Blockingou Async. E continua trabalhando em outros etc.

Os resultados dessa execução são os seguintes:

acdb

Então, basicamente, o segundo setTimeoutfoi concluído primeiro e sua função de retorno de chamada é executada antes do primeiro, e isso faz sentido.

Estamos falando de aplicativos single-threaded aqui. O JS Engine continua executando isso e, a menos que termine a primeira solicitação, ela não passará para a segunda. Mas o bom é que não esperará que as operações de bloqueio setTimeoutsejam resolvidas, para que seja mais rápido porque aceita as novas solicitações recebidas.

Mas minhas perguntas surgem em torno dos seguintes itens:

# 1: Se estamos falando de um aplicativo de thread único, qual mecanismo processa setTimeoutsenquanto o mecanismo JS aceita mais solicitações e as executa? Como o thread único continua trabalhando em outras solicitações? O que funciona setTimeoutenquanto outras solicitações continuam chegando e são executadas.

# 2: Se essas setTimeoutfunções são executadas nos bastidores enquanto mais solicitações são recebidas e executadas, o que executa as execuções assíncronas nos bastidores? Sobre o que falamos sobre o que é chamado de EventLoop?

# 3: Mas o método inteiro não deve ser colocado no EventLoopmodo para que tudo seja executado e o método de retorno de chamada seja chamado? Isto é o que eu entendo quando falo sobre funções de retorno de chamada:

function downloadFile(filePath, callback)
{
  blah.downloadFile(filePath);
  callback();
}

Mas, neste caso, como o JS Engine sabe se é uma função assíncrona para poder colocar o retorno de chamada na EventLoop? Perhaps something like thepalavra-chave async` em C # ou algum tipo de atributo que indica que o método que o JS Engine adotará é um método assíncrono e deve ser tratado em conformidade.

# 4: Mas um artigo diz muito contrário ao que eu estava imaginando sobre como as coisas podem estar funcionando:

O loop de eventos é uma fila de funções de retorno de chamada. Quando uma função assíncrona é executada, a função de retorno de chamada é enviada para a fila. O mecanismo JavaScript não inicia o processamento do loop de eventos até que o código após a execução de uma função assíncrona.

# 5: E existe esta imagem aqui que pode ser útil, mas a primeira explicação na imagem está dizendo exatamente a mesma coisa mencionada na pergunta número 4:

insira a descrição da imagem aqui

Então, minha pergunta aqui é obter alguns esclarecimentos sobre os itens listados acima?

Tarik
fonte
1
Threads não são a metáfora certa para lidar com esses problemas. Pense em eventos.
Denys Séguret
1
@dystroy: Um exemplo de código para ilustrar essa metáfora do evento em JS seria bom de ver.
Tarik
Não vejo qual é exatamente a sua pergunta aqui.
Denys Séguret
1
@dystroy: Minha pergunta aqui é obter alguns esclarecimentos sobre os itens listados acima?
Tarik
2
O nó não é um thread único, mas isso não importa para você (além do fato de que ele consegue fazer outras coisas enquanto o código do usuário é executado). Apenas um retorno de chamada no máximo no seu código de usuário é executado por vez.
Denys Séguret

Respostas:

85

1: Se estamos falando de um aplicativo de thread único, quais processos setTimeouts enquanto o mecanismo JS aceita mais solicitações e as executa? Esse encadeamento único não continuará funcionando em outras solicitações? Então, quem continuará trabalhando no setTimeout enquanto outras solicitações continuarão sendo executadas.

Há apenas um encadeamento no processo do nó que realmente executará o JavaScript do seu programa. No entanto, no próprio nó, na verdade, existem vários threads manipulando a operação do mecanismo de loop de eventos, e isso inclui um conjunto de threads de E / S e vários outros. A chave é que o número desses encadeamentos não corresponde ao número de conexões simultâneas sendo manipuladas como faria em um modelo de simultaneidade de encadeamento por conexão.

Agora, sobre "executar setTimeouts", quando você invoca setTimeout, tudo o que faz é basicamente atualizar uma estrutura de dados de funções a serem executadas em um momento no futuro. Basicamente, ele tem várias filas de coisas que precisam ser executadas e cada "marca" do loop de eventos seleciona uma, remove-a da fila e a executa.

Uma coisa importante a entender é que o nó depende do sistema operacional durante a maior parte do trabalho pesado. Portanto, as solicitações de rede recebidas são realmente rastreadas pelo próprio sistema operacional e, quando o nó está pronto para lidar com uma, apenas usa uma chamada do sistema para solicitar ao sistema operacional uma solicitação de rede com dados prontos para serem processados. Grande parte do nó "trabalho" de E / S faz é "Ei, SO, conseguiu uma conexão de rede com os dados prontos para leitura?" ou "Ei, SO, alguma das minhas chamadas pendentes do sistema de arquivos tem dados prontos?". Com base no seu algoritmo interno e no design do mecanismo de loop de eventos, o nó selecionará um "tick" de JavaScript para executar, executá-lo e, em seguida, repita o processo novamente. É isso que significa o loop de eventos. O nó está basicamente determinando o tempo todo "qual é o próximo pedacinho de JavaScript que devo executar?" E depois executá-lo.setTimeoutou process.nextTick.

2: Se esses setTimeout serão executados nos bastidores enquanto mais solicitações forem recebidas e executadas, a coisa que executa as execuções assíncronas nos bastidores é a que estamos falando sobre EventLoop?

Nenhum JavaScript é executado nos bastidores. Todo o JavaScript em seu programa é executado na frente e no centro, um de cada vez. O que acontece nos bastidores é que o SO lida com as E / S e o nó aguarda que esteja pronto e o nó gerencia sua fila de javascript aguardando a execução.

3: Como o JS Engine pode saber se é uma função assíncrona para poder colocá-la no EventLoop?

Há um conjunto fixo de funções no núcleo do nó que são assíncronas porque fazem chamadas ao sistema e o nó sabe quais são porque precisam chamar o SO ou C ++. Basicamente, todas as E / S da rede e do sistema de arquivos, bem como as interações do processo filho, serão assíncronas e a única maneira que o JavaScript pode fazer com que o nó execute algo de forma assíncrona é invocando uma das funções assíncronas fornecidas pela biblioteca principal do nó. Mesmo se você estiver usando um pacote npm que define sua própria API, para gerar o loop de eventos, eventualmente o código do pacote npm chamará uma das funções assíncronas do núcleo do nó e é quando o nó sabe que o tick está completo e pode iniciar o evento algoritmo de loop novamente.

4 O loop de eventos é uma fila de funções de retorno de chamada. Quando uma função assíncrona é executada, a função de retorno de chamada é enviada para a fila. O mecanismo JavaScript não inicia o processamento do loop de eventos até que o código após a execução de uma função assíncrona.

Sim, isso é verdade, mas é enganador. A principal coisa é o padrão normal é:

//Let's say this code is running in tick 1
fs.readFile("/home/barney/colors.txt", function (error, data) {
  //The code inside this callback function will absolutely NOT run in tick 1
  //It will run in some tick >= 2
});
//This code will absolutely also run in tick 1
//HOWEVER, typically there's not much else to do here,
//so at some point soon after queueing up some async IO, this tick
//will have nothing useful to do so it will just end because the IO result
//is necessary before anything useful can be done

Então, sim, você pode bloquear totalmente o loop de eventos contando os números de Fibonacci de forma síncrona, todos na memória, todos no mesmo tick, e sim, isso congelaria totalmente seu programa. É simultaneidade cooperativa. Cada marca de JavaScript deve gerar o loop de eventos dentro de um período de tempo razoável ou a arquitetura geral falha.

Peter Lyons
fonte
1
Digamos que eu tenho uma fila que levará 1 minuto para o servidor executar, e a primeira coisa foi alguma função assíncrona que terminou após 10 segundos. Irá para o final da fila ou se empurrará para a linha assim que estiver pronto?
Ilyo 18/08
4
Geralmente ele vai para o final da fila, mas a semântica de process.nextTickvs setTimeoutvs setImmediateé sutilmente diferente, embora você realmente não deva se preocupar. Eu tenho uma postagem no blog chamada setTimeout e amigos que entra em mais detalhes.
Peter Lyons
Você pode por favor elaborar? Digamos que eu tenha dois retornos de chamada e o primeiro tenha um método changeColor com tempo de execução de 10mS e um setTimeout de 1 minuto e o segundo tenha um método changeBackground com tempo de execução de 50ms com um setTimeout de 10 segundos. Sinto que o changeBackground estará na Fila primeiro e o changeColor será o próximo. Depois disso, o loop Event seleciona os métodos de forma síncrona. Estou certo?
SheshPai
1
@SheshPai é muito confuso para todos discutirem códigos quando escritos em parágrafos em inglês. Basta postar uma nova pergunta com um trecho de código para que as pessoas possam responder com base no código, em vez de uma descrição do código, o que deixa muita ambiguidade.
Peter Lyons
youtube.com/watch?v=QyUFheng6J0&spfreload=5 esta é outra boa explicação de JavaScript Motor
Mukesh Kumar
65

Existe um fantástico tutorial em vídeo de Philip Roberts, que explica o loop de eventos javascript da maneira mais simplista e conceitual. Todo desenvolvedor de javascript deve dar uma olhada.

Aqui está o link do vídeo no Youtube.

sam100rav
fonte
16
Eu assisti e foi realmente a melhor explicação de todas.
Tarik
1
Um vídeo obrigatório para fãs e entusiastas de JavaScript.
Nirus
1
este vídeo muda meu show ^^
HuyTran
1
veio passear aqui .. e esta é uma das melhores explicações que tenho .. obrigado por compartilhar ...: D
RohitS
1
Essa foi uma eyeopener
HebleV
11

Não pense que o processo host seja de thread único, eles não são. O que é thread único é a parte do processo host que executa seu código javascript.

Exceto trabalhadores em segundo plano , mas estes complicam o cenário ...

Portanto, todo o seu código js é executado no mesmo encadeamento, e não há possibilidade de que duas partes diferentes do seu código js sejam executadas simultaneamente (portanto, você não precisa gerenciar o nigthmare de concorrência).

O código js que está executando é o último código que o processo do host captou do loop de eventos. No seu código, você pode basicamente fazer duas coisas: executar instruções síncronas e agendar funções a serem executadas no futuro, quando alguns eventos acontecerem.

Aqui está minha representação mental (cuidado: é só isso, não conheço os detalhes de implementação do navegador!) Do seu código de exemplo:

console.clear();                                   //exec sync
console.log("a");                                  //exec sync
setTimeout(                //schedule inAWhile to be executed at now +1 s 
    function inAWhile(){
        console.log("b");
    },1000);    
console.log("c");                                  //exec sync
setTimeout(
    function justNow(){          //schedule justNow to be executed just now
        console.log("d");
},0);       

Enquanto seu código está em execução, outro encadeamento no processo host monitora todos os eventos do sistema que estão ocorrendo (cliques na interface do usuário, arquivos lidos, pacotes de redes recebidos etc.)

Quando seu código é concluído, ele é removido do loop de eventos e o processo do host retorna para verificá-lo, para verificar se há mais código para executar. O loop de eventos contém mais dois manipuladores de eventos: um a ser executado agora (a função justNow) e outro em um segundo (a função inAWhile).

O processo host agora tenta corresponder a todos os eventos para ver se existem manipuladores registrados para eles. Ele descobriu que o evento que justNow está esperando aconteceu, então ele começou a executar seu código. Quando a função justNow sai, ele verifica o loop de eventos outra vez, procurando por manipuladores de eventos. Supondo que 1 s tenha passado, ele executa a função inAWhile e assim por diante ....

Andrea Parodi
fonte