O Node possui um paradigma completamente diferente e, uma vez capturado corretamente, é mais fácil ver essa forma diferente de resolver problemas. Você nunca precisa de vários threads em um aplicativo Node (1) porque você tem uma maneira diferente de fazer a mesma coisa. Você cria vários processos; mas é muito diferente, por exemplo, de como o Prefork mpm do Apache Web Server faz.
Por enquanto, vamos pensar que temos apenas um núcleo de CPU e vamos desenvolver um aplicativo (no jeito do Node) para fazer algum trabalho. Nosso trabalho é processar um arquivo grande executando seu conteúdo byte a byte. A melhor maneira de nosso software é começar o trabalho desde o início do arquivo, acompanhando byte a byte até o final.
- Ei, Hasan, suponho que você seja um novato ou muito antiquado da época do meu avô !!! Por que você não cria alguns tópicos e os torna muito mais rápidos?
- Oh, nós temos apenas um núcleo de CPU.
-- E daí? Cria alguns tópicos cara, torna mais rápido!
-- Não funciona assim. Se eu criar tópicos, o tornarei mais lento. Porque estarei adicionando muita sobrecarga ao sistema para alternar entre os threads, tentando dar a eles uma quantidade justa de tempo, e dentro do meu processo, tentando se comunicar entre esses threads. Além de todos esses fatos, também terei que pensar em como vou dividir um único trabalho em várias partes que podem ser feitas em paralelo.
- Ok ok, vejo que você é pobre. Vamos usar meu computador, ele tem 32 núcleos!
- Nossa, você é demais meu caro amigo, muito obrigado. Eu agradeço!
Então voltamos ao trabalho. Agora temos 32 núcleos de CPU graças ao nosso amigo rico. As regras que devemos obedecer acabam de mudar. Agora queremos utilizar toda essa riqueza que recebemos.
Para usar vários núcleos, precisamos encontrar uma maneira de dividir nosso trabalho em partes que possamos manipular em paralelo. Se não fosse Node, usaríamos threads para isso; 32 threads, um para cada núcleo de CPU. Porém, como temos o Node, criaremos 32 processos de Node.
Threads podem ser uma boa alternativa aos processos do Node, talvez até uma maneira melhor; mas apenas em um tipo específico de trabalho onde o trabalho já está definido e temos total controle sobre como lidar com ele. Fora isso, para todos os outros tipos de problema em que o trabalho vem de fora de uma maneira que não temos controle e queremos responder o mais rápido possível, o jeito de Node é indiscutivelmente superior.
- Ei, Hasan, você ainda está trabalhando single-threaded? O que há de errado com você, cara? Acabei de fornecer o que você queria. Você não tem mais desculpas. Crie threads, faça com que funcionem mais rápido.
- Eu dividi o trabalho em partes e cada processo funcionará em uma dessas partes em paralelo.
- Por que você não cria tópicos?
- Desculpe, não acho que seja utilizável. Você pode levar seu computador se quiser?
- Não ok, estou legal, só não entendo porque você não usa fios?
- Obrigado pelo computador. :) Já dividi o trabalho em partes e crio processos para trabalhar essas peças em paralelo. Todos os núcleos da CPU serão totalmente utilizados. Eu poderia fazer isso com threads em vez de processos; mas o Node tem esse caminho e meu chefe Parth Thakkar quer que eu use o Node.
- Ok, me avise se precisar de outro computador. : p
Se eu criar 33 processos, ao invés de 32, o escalonador do sistema operacional vai pausar um thread, iniciar o outro, pausar após alguns ciclos, iniciar o outro novamente ... Isso é overhead desnecessário. Eu não quero isso. Na verdade, em um sistema com 32 núcleos, eu nem gostaria de criar exatamente 32 processos, 31 podem ser mais agradáveis . Porque não é apenas meu aplicativo que funcionará neste sistema. Deixar um pouco de espaço para outras coisas pode ser bom, especialmente se tivermos 32 quartos.
Acredito que estamos na mesma página agora sobre a utilização total de processadores para tarefas que exigem muito da CPU .
- Hmm, Hasan, me desculpe por zombar um pouco de você. Eu acredito que entendo você melhor agora. Mas ainda há algo para o qual preciso de uma explicação: por que tanto alvoroço sobre a execução de centenas de threads? Eu li em todos os lugares que threads são muito mais rápidos de criar e burros do que processos de bifurcação. Você bifurca processos em vez de threads e acha que é o máximo que obteria com o Node. Então o Node não é apropriado para este tipo de trabalho?
- Não se preocupe, eu também estou bem. Todo mundo diz essas coisas, então acho que estou acostumado a ouvi-las.
-- Assim? O Node não é bom para isso?
- O Node é perfeitamente bom para isso, embora os threads também possam ser bons. Quanto à sobrecarga de criação de thread / processo; nas coisas que você repete muito, cada milissegundo conta. No entanto, eu crio apenas 32 processos e isso vai demorar um pouco. Isso vai acontecer apenas uma vez. Não fará nenhuma diferença.
- Quando eu quero criar milhares de tópicos, então?
- Você nunca quer criar milhares de tópicos. No entanto, em um sistema que está executando um trabalho que vem de fora, como um servidor da Web processando solicitações HTTP; se você estiver usando um thread para cada solicitação, estará criando muitos threads, muitos deles.
- O Node é diferente? Certo?
-- Sim, exatamente. É aqui que o Node realmente brilha. Como um thread é muito mais leve que um processo, uma chamada de função é muito mais leve que um thread. O nó chama funções, em vez de criar threads. No exemplo de um servidor da web, cada solicitação recebida causa uma chamada de função.
-- Hmm interessante; mas você só pode executar uma função ao mesmo tempo se não estiver usando vários threads. Como isso pode funcionar quando muitas solicitações chegam ao servidor da web ao mesmo tempo?
- Você está perfeitamente certo sobre como as funções funcionam, uma de cada vez, nunca duas em paralelo. Quero dizer, em um único processo, apenas um escopo de código está sendo executado por vez. O OS Scheduler não vem e faz uma pausa nesta função e muda para outra, a menos que faça uma pausa no processo para dar tempo a outro processo, não a outro thread em nosso processo. (2)
- Então, como um processo pode lidar com 2 solicitações por vez?
- Um processo pode lidar com dezenas de milhares de solicitações ao mesmo tempo, desde que nosso sistema tenha recursos suficientes (RAM, rede, etc.). Como essas funções funcionam é A DIFERENÇA CHAVE.
- Hmm, devo estar animado agora?
- Talvez :) Node executa um loop em uma fila. Nessa fila estão nossos jobs, ou seja, as chamadas que iniciamos para processar as solicitações recebidas. O ponto mais importante aqui é a maneira como projetamos nossas funções para serem executadas. Em vez de começar a processar uma solicitação e fazer o chamador esperar até terminarmos o trabalho, encerramos rapidamente nossa função depois de realizar uma quantidade de trabalho aceitável. Quando chegamos a um ponto em que precisamos esperar que outro componente faça algum trabalho e nos retorne um valor, em vez de esperar por isso, simplesmente terminamos nossa função adicionando o resto do trabalho à fila.
- Parece muito complexo?
- Não, não, posso parecer complexo; mas o sistema em si é muito simples e faz todo o sentido.
Agora eu quero parar de citar o diálogo entre esses dois desenvolvedores e terminar minha resposta após um último exemplo rápido de como essas funções funcionam.
Desta forma, estamos fazendo o que o OS Scheduler faria normalmente. Pausamos nosso trabalho em algum ponto e deixamos outras chamadas de função (como outras threads em um ambiente multi-thread) serem executadas até chegarmos a nossa vez novamente. Isso é muito melhor do que deixar o trabalho para o OS Scheduler, que tenta dar apenas um tempo para cada thread no sistema. Sabemos o que estamos fazendo muito melhor do que o OS Scheduler e espera-se que paremos quando deveríamos parar.
Abaixo está um exemplo simples onde abrimos um arquivo e o lemos para fazer algum trabalho nos dados.
Forma síncrona:
Open File
Repeat This:
Read Some
Do the work
Forma assíncrona:
Open File and Do this when it is ready: // Our function returns
Repeat this:
Read Some and when it is ready: // Returns again
Do some work
Como você pode ver, nossa função pede ao sistema para abrir um arquivo e não espera que ele seja aberto. Ele termina fornecendo as próximas etapas após o arquivo estar pronto. Quando retornamos, o Node executa outras chamadas de função na fila. Depois de percorrer todas as funções, o loop de eventos passa para a próxima curva ...
Em resumo, o Node tem um paradigma completamente diferente do desenvolvimento multi-threaded; mas isso não significa que lhe faltem coisas. Para um trabalho síncrono (onde podemos decidir a ordem e a forma de processamento), funciona bem como o paralelismo multi-thread. Para um trabalho que vem de fora, como solicitações para um servidor, ele simplesmente é superior.
(1) A menos que você esteja construindo bibliotecas em outras linguagens como C / C ++, caso em que você ainda não cria threads para dividir tarefas. Para este tipo de trabalho, você tem dois threads, um dos quais continuará a comunicação com o Node enquanto o outro faz o trabalho real.
(2) Na verdade, cada processo do Node tem vários threads pelos mesmos motivos que mencionei na primeira nota de rodapé. No entanto, isso não é como 1000 threads fazendo trabalhos semelhantes. Esses threads extras são para coisas como aceitar eventos de E / S e lidar com mensagens entre processos.
ATUALIZAÇÃO (como resposta a uma boa pergunta nos comentários)
@Mark, obrigado pela crítica construtiva. No paradigma do Node, você nunca deve ter funções que demoram muito para processar, a menos que todas as outras chamadas na fila sejam projetadas para serem executadas uma após a outra. No caso de tarefas computacionalmente caras, se olharmos a imagem completa, vemos que não se trata de uma questão de "Devemos usar threads ou processos?" mas uma questão de "Como podemos dividir essas tarefas de uma maneira bem equilibrada em subtarefas para que possamos executá-las em paralelo, empregando vários núcleos de CPU no sistema?" Digamos que processaremos 400 arquivos de vídeo em um sistema com 8 núcleos. Se quisermos processar um arquivo de cada vez, precisamos de um sistema que processe diferentes partes do mesmo arquivo, caso em que, talvez, um sistema multi-threaded de processo único seja mais fácil de construir e ainda mais eficiente. Ainda podemos usar o Node para isso, executando vários processos e passando mensagens entre eles quando o compartilhamento de estado / comunicação for necessário. Como eu disse antes, uma abordagem multiprocessos com Node ébem como uma abordagem multi-thread neste tipo de tarefas; mas não mais do que isso. Novamente, como eu disse antes, a situação que o Node brilha é quando temos essas tarefas chegando como entrada para o sistema de várias fontes, já que manter muitas conexões simultaneamente é muito mais fácil no Node em comparação com um thread por conexão ou processo por conexão sistema.
Quanto às setTimeout(...,0)
ligações; às vezes, pode ser necessário dar uma pausa durante uma tarefa demorada para permitir que as chamadas na fila tenham sua parcela de processamento. Dividir tarefas de maneiras diferentes pode salvá-lo disso; mas ainda assim, isso não é realmente um hack, é apenas a forma como as filas de eventos funcionam. Além disso, usar process.nextTick
para este fim é muito melhor, pois quando você usa setTimeout
, o cálculo e a verificação do tempo passado serão necessários enquanto process.nextTick
é simplesmente o que realmente queremos: "Ei tarefa, volte para o fim da fila, você usou o seu compartilhamento! "