O NodeJs é de thread único?

Mar 20 2023
Como trabalhar com outros threads no Nodejs?
O Node.js executa o código JavaScript em um único thread, o que significa que seu código pode executar apenas uma tarefa por vez.
Processo de E/S do Nodejs

O Node.js executa o código JavaScript em um único thread, o que significa que seu código pode executar apenas uma tarefa por vez. Mas o próprio Node.js é multiencadeado e fornece encadeamentos ocultos por meio da biblioteca `libuv`, que lida com operações de E/S, como ler arquivos de um disco ou solicitações de rede. Por meio do uso de encadeamentos ocultos, o Node.js fornece métodos assíncronos que permitem que seu código faça solicitações de E/S sem bloquear o encadeamento principal.

O Node.js apresentou o módulo worker-threads para nós. Esse módulo permite criar threads e executar tarefas Javascript em paralelo. Se um thread terminar uma tarefa, ele enviará uma mensagem para o thread principal que contém o resultado para que possamos usá-lo no thread principal. Os threads de trabalho não bloqueiam o thread principal, portanto, podemos dividir e distribuir nossa tarefa para vários pars para otimizá-la.

Vamos fazer um exemplo disso e olhá-lo mais de perto.

Estamos criando um projeto Nodejs e instalando o framework Express

mkdir multiple-thread
cd multiple-thread
npm init -y
npm install express

Em primeiro lugar, existem processos e threads. Devemos saber as diferenças entre eles.

O que é Processo?

Um processo está executando um programa no sistema operacional. Ele pode executar uma tarefa por vez.

Vamos olhar mais de perto o processo mais de perto. Crie um arquivo JS e registre o processo.

//ex-process.js

const processName = process.argv.slice(2)[0]

let count = 0;

while(true){
 count++;
 if(count === 3333 || count=== 6666){
   console.log(`${processName}: ${count}`)
 }
}

node ex-process.js FirstEx &

// & symbol means the Node program to run in the background, so we can use 
our teminal for other works

Output
[1] 82942
FirstEx: 3333
FirstEx: 6666

ps |grep node

Output
82942 ttys001    2:38.85 node ex-process.js FirstEx
83105 ttys001    0:00.00 grep node

Threads são uma maneira relativamente leve de implementar vários caminhos de execução dentro de um aplicativo. No nível do sistema, os programas são executados lado a lado, com o sistema distribuindo o tempo de execução para cada programa com base em suas necessidades e nas necessidades de outros programas.

Vamos codificar o Express Server com a rota sem bloqueio e com as rotas de bloqueio

Vamos codificar uma rota de bloqueio normal para os usuários. Em app.js;

const express = require('express');

const app = express();

app.get('/hello', (req,res)=>{
    res.status(200).send(`Hello`);
})

app.get('/blocking-route', (req,res)=>{
    console.log('Blocking route called')
    let counter = 0;
    for (let i = 0; i < 15_000_000_000; i++) {
      counter++;
    }
    res.status(200).send(`result is ${counter}`);
})



app.listen(7333, ()=> console.log('Server started on port 7333'));

Ele não responde até blocking-routeterminar e enviar uma resposta.

Por que não nos respondeu instantaneamente?

Neste exemplo, o loop for vinculado à CPU bloqueia o thread principal. Enquanto o thread principal está bloqueado, o Node.js não pode processar nenhuma consulta até que o processo vinculado à CPU tenha terminado. portanto, nenhuma das outras rotas /hellofunciona como a /blocking-routerota é carregada.

Assim, se tivermos uma API que tenha milhares de GETrequisições simultâneas para a rota não bloqueante como /hello, apenas uma requisição para o /blocking-routeirá bloquear todas as rotas na API. Isso significa que todas as rotas de aplicativos não respondem.

DAAA IT's javascript, não podemos usar outro thread

Dudeeee Não é só JS. É o Nodejs. Podemos descarregar uma tarefa vinculada à CPU com threads de trabalho.

Antes de tudo, devemos verificar nossos núcleos com nproco comando

nproc

output
8

Vamos criar outro arquivo JS como worker.js, o nome fica a seu critério. Neste arquivo, fazemos tarefas intensivas de CPU

//worker.js
const { parentPort } = require("worker_threads");

let counter = 0;
console.log("Worker started");
for (let i = 0; i < 15_000_000_000; i++) {
  counter++;
}

parentPort.postMessage(counter);

//app.js
const express = require("express");
const { Worker } = require("worker_threads");

const app = express();

app.get("/blocking-route", async (req, res) => {
  console.log("Blocking route called");
  let counter = 0;
  for (let i = 0; i < 15_000_000_000; i++) {
    counter++;
  }
  res.status(200).send(`result is ${counter}`);
});

app.get("/hello", (req, res) => {
  res.status(200).send(`Hello`);
});

app.get("/non-blocking-route", async (req, res) => {
  console.log("Non-blocking route called");
  const worker = new Worker("./worker.js");

  worker.on("message", (data) => {
    res.status(200).send(`result is ${data}`);
  });

  worker.on("error", (err) => {
    res.status(404).send(` error ${err}`);
  });

  console.log("I am not blocked");
});

app.listen(7333, () => console.log("Server started on port 7333"));

Vamos iniciar o servidor com node app.js. Abra seu navegador e vá http://localhost:7333/non-blocking-routeantes que ele retorne uma resposta, vá para http://localhost:7333/hello. Você percebeu isso? Recebemos uma resposta instantaneamente. Não há bloqueio. Não esperamos que as outras rotas terminassem de carregar. Porque a tarefa vinculada à CPU é descarregada e está trabalhando em outro thread, e o thread principal lida com todas as solicitações. Podemos usar mais trabalhadores para melhorar nosso desempenho. Por exemplo, tenho 8 núcleos e posso dividir meu Processo em 8.

Conclusão

Trabalhadores (threads) podem ajudar com operações JavaScript com uso intensivo de CPU. Ao contrário child_processde ou cluster, worker_threadspode compartilhar memória.

Neste artigo, tentei facilitar e explicar como funcionam os worker_threads?

Obrigado por ler

>REFs