¿Es NodeJs de un solo subproceso?

Mar 20 2023
¿Cómo trabajar con otros hilos en Nodejs?
Node.js ejecuta código JavaScript en un solo hilo, lo que significa que su código solo puede realizar una tarea a la vez.
Proceso de E/S de Nodejs

Node.js ejecuta código JavaScript en un solo hilo, lo que significa que su código solo puede realizar una tarea a la vez. Pero Node.js en sí es multiproceso y proporciona subprocesos ocultos a través de la biblioteca `libuv`, que maneja operaciones de E/S como leer archivos desde un disco o solicitudes de red. Mediante el uso de subprocesos ocultos, Node.js proporciona métodos asincrónicos que permiten que su código realice solicitudes de E/S sin bloquear el subproceso principal.

Node.js nos presentó el módulo de subprocesos de trabajo. Ese módulo permite crear hilos y ejecutar tareas de Javascript en paralelo. Si un hilo termina una tarea, envía un mensaje al hilo principal que contiene el resultado para que podamos usarlo en el hilo principal. Los subprocesos de trabajo no bloquean el subproceso principal, por lo que podemos dividir y distribuir nuestra tarea en varios pares para optimizarla.

Hagamos un ejemplo de ello y mirémoslo más de cerca.

Estamos creando un proyecto Nodejs e instalando Express Framework

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

En primer lugar, hay procesos e hilos. Debemos saber las diferencias entre ellos.

¿Qué es Proceso?

Un proceso está ejecutando un programa en el sistema operativo. Puede ejecutar una tarea a la vez.

Miremos más de cerca el proceso más de cerca. Cree un archivo JS y registre el proceso.

//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

Los subprocesos son una forma relativamente ligera de implementar múltiples rutas de ejecución dentro de una aplicación. A nivel del sistema, los programas se ejecutan uno al lado del otro, y el sistema distribuye el tiempo de ejecución a cada programa en función de sus necesidades y las necesidades de otros programas.

Codifiquemos Express Server con la ruta sin bloqueo y las rutas con bloqueo

Codifiquemos una ruta de bloqueo normal para los usuarios. en 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'));

No responde hasta que blocking-routefinaliza y ha enviado una respuesta.

¿Por qué no nos respondió al instante?

En este ejemplo, el bucle for vinculado a la CPU bloquea el subproceso principal. Mientras el subproceso principal está bloqueado, Node.js no puede procesar ninguna consulta hasta que finalice el proceso vinculado a la CPU. por lo que ninguna de las otras rutas /hellofunciona como la /blocking-routeruta se carga.

Por lo tanto, si tenemos una API que tiene miles de GETsolicitudes simultáneas a la ruta sin bloqueo como /hello, solo una solicitud bloqueará /blocking-routetodas las rutas en la API. Eso significa que hace que todas las rutas de la aplicación no respondan.

DAAA IT es javascript, no podemos usar otro hilo

Dudeeee No es sólo JS. Es Nodejs. Podemos descargar una tarea vinculada a la CPU con subprocesos de trabajo.

En primer lugar, debemos comprobar nuestros núcleos con nprocel comando

nproc

output
8

Vamos a crear otro archivo JS como worker.js, el nombre depende de usted. En este archivo, hacemos una tarea intensiva 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"));

Comencemos el servidor con node app.js. Abra su navegador y vaya http://localhost:7333/non-blocking-routeantes de que devuelva una respuesta, vaya a http://localhost:7333/hello. ¿Te diste cuenta? Obtenemos una respuesta al instante. No hay bloqueo. No esperamos a que las otras rutas terminaran de cargarse. Porque la tarea vinculada a la CPU se descarga y está trabajando en otro subproceso, y el subproceso principal maneja todas las solicitudes. Podemos utilizar más trabajadores para mejorar nuestro rendimiento. Por ejemplo, tengo 8 núcleos y puedo dividir mi Proceso en 8.

Conclusión

Los trabajadores (subprocesos) pueden ayudar con las operaciones de JavaScript que hacen un uso intensivo de la CPU. A diferencia de child_processo cluster, worker_threadspuede compartir memoria.

En este artículo, he intentado hacerlo más fácil y explicar ¿Cómo funciona worker_threads?

Gracias por leer

>REF