Consejos Tecnológicos

¿Cómo se inicia el proceso hijo de Node.JS?

Probablemente haya escuchado o leído que Node.js es de un solo subproceso. Aunque esto es cierto, hay algunos matices en esta afirmación. En este artículo, comprendamos rápidamente la arquitectura de subprocesos en Node.js, comprendamos sus limitaciones y analicemos cuatro tecnologías diferentes que podemos usar para superar estas limitaciones.

Las sutiles diferencias entre Node.js y los subprocesos:

JavaScript es un lenguaje de un solo subproceso. Dado que Node.js es más como un entorno para ejecutar JavaScript fuera del navegador, podemos decir que Node.js también es de un solo subproceso.

Sin embargo, es importante comprender que, aunque el código que escribimos se ejecuta en un solo hilo y tiene un bucle de eventos en el mismo hilo, algunas operaciones realizadas por algunas bibliotecas de Nodo que interactúan con el sistema operativo subyacente pueden usar métodos multiproceso.

Por ejemplo, la biblioteca de cifrado en Node usa libuv Biblioteca interna, escrita en C, y libuv Puede acceder al grupo de subprocesos. Por lo tanto, las operaciones realizadas por la biblioteca de cifrado se pueden ejecutar de una manera multiproceso.

Limitaciones de Node.js:

Debido a que nuestro código se ejecuta en un solo subproceso, las operaciones intensivas de la CPU en nuestra lógica de código bloquean el subproceso de eventos, lo que aumenta la latencia del servidor y reduce su rendimiento.

Un ejemplo de una tarea de uso intensivo de la CPU podría ser analizar una hoja de cálculo de Excel enorme, procesar los datos que contiene y escribir los resultados en una nueva hoja de cálculo.

Generalmente, Node.js no es la herramienta elegida para este tipo de trabajo. Pero supongamos que tenemos algunas limitaciones técnicas de pila y solo podemos usar Node.js. Entonces, debemos encontrar una manera de asegurarnos de que este proceso computacionalmente intensivo no retrase nuestro hilo.

Publicaciones relacionadas

Algunas soluciones para resolver la naturaleza de un solo subproceso:

El método más común es usar la biblioteca de clústeres en Node.js para multiprocesamiento. El segundo método común es el método multiproceso que utiliza subprocesos de trabajo.

En este artículo, estudiaremos varios métodos que utilizan métodos de multiprocesamiento. No discutiremos el módulo de clúster, pero veremos el método fork en el módulo `child_process` en Node, que es usado internamente por el módulo de clúster para implementar multiprocesamiento.

El módulo `child_process` puede generar procesos secundarios del proceso principal. De forma predeterminada, todos los procesos secundarios generados establecerán una canalización con el proceso principal del flujo estándar, a saber: `Salida estándar`,`Entrada estándar`y`Error estándar«.

Varias API para multiprocesamiento en el módulo Node Child_process:

Node maneja múltiples procesos a través de un módulo llamado «child_process». Como su nombre sugiere, genera procesos secundarios del proceso principal. Aquí hay cuatro formas diferentes de usar módulos para crear procesos secundarios:

  1. desove()
  2. implementar()
  3. execFile () y
  4. tenedor()

Discutiremos brevemente los principios de funcionamiento de estos cuatro métodos y las diferencias entre ellos.

desove:

Spawn es una función exportada por el módulo `child_process`, por lo que podemos usarla en nuestro código de las siguientes formas:

const spawn = require('child_process');   
const lsCommand = spawn('ls');

Cuando se llama al método spawn, se crea un proceso hijo y se ejecuta el comando pasado como primer parámetro a la función spawn en el proceso creado. En el ejemplo anterior, el comando `ls` usado para listar todos los archivos y directorios en la máquina Linux se ejecutará en el nuevo proceso hijo.

Opcionalmente, spawn también acepta un segundo parámetro, que es una matriz de parámetros para pasar el comando. De forma predeterminada, será una matriz vacía.

Supongamos que también necesitamos mostrar archivos ocultos. En este caso, normalmente pasamos el modificador `-a` al comando` ls`. Podemos hacer esto a través de la función spawn, de la siguiente manera:

const lsCommand = spawn('ls',['-a']);

Curiosamente, la nueva instancia de proceso hijo creada por spawn implementa la API EventEmitter, por lo que podemos adjuntar oyentes para diferentes eventos en varias transmisiones (como stdin, stdout y stderr).

En el siguiente fragmento de código, veamos cómo adjuntar un oyente al evento de datos en la secuencia stdout en el proceso creado por la función spawn:

const spawn = require('child_process'); 
  
const lsCommand = spawn('ls', ['-a']); 
  
console.log(`main process id: $process.pid`); 
console.log(`spawned child process id: $lsCommand.pid`); 
  
lsCommand.stdout.on('data',(data)=>console.log(`Output from spawned child process with id $lsCommand.pid:n $data`));

La ejecución del fragmento de código anterior es la siguiente:

implementar:

La función ejecutiva también crea un proceso hijo. Sin embargo, a diferencia de la función spawn, primero crea un shell y luego ejecuta el comando que se le proporciona como primer parámetro.

El primer parámetro puede ser un comando o la ubicación de un archivo de secuencia de comandos que desea ejecutar en el shell generado del proceso hijo.

Podemos usar una devolución de llamada como segundo parámetro, que almacena en búfer la salida del comando ejecutado de los flujos de salida estándar (stdout) y error estándar (stderr).

Veamos un fragmento de código con exec ():

const  exec  = require('child_process'); 
  
const execCallback = (error, stdout, stderr)=> 
  if (error)  
    console.error(`exec error: $error`); 
    return; 
   
  console.log(`standard output: $stdout`); 
  console.log(`standard error: $stderr`); 
 
  
const catCommand = exec('cat spawn-demo.js | wc -w', execCallback); 
console.log(`Main Process Id: $process.pid`); 
console.log(`child process Id: $catCommand.pid`);

Como se puede ver en el fragmento de código anterior, dado que el comando que pasamos a exec se ejecutará en el shell, podemos usar la canalización del shell `|` para combinar varios comandos. En el ejemplo de código anterior, podemos imprimir el contenido del archivo spawn-demo.js y contar el número de palabras en la salida.

Archivo ejecutable:

La función execFile se diferencia de la función exec en que no crea un shell en el que se puedan ejecutar los comandos proporcionados. Por tanto, si queremos ejecutar algunos scripts en segundo plano sin bloquear los hilos de nuestra aplicación, podemos utilizarlo como una opción.

Supongamos que tenemos un archivo de script bash de demostración llamado `versions.sh` de la siguiente manera:

# versions.sh 
#!/bin/bash 

echo "printing different software versions" 
echo "node version:  $(node -v)" 
echo "npm version: $(npm -v)" 
echo "script run complete"

Ahora, podemos usar la función execFile para ejecutar este script en un proceso secundario separado sin crear un shell, como se muestra a continuación:

const execFile = require('child_process');
 
const versionsScriptRunner = execFile('./versions.sh',(err,stdout)=>
  if(err)
    console.log("script file failed to be executed");
    return;
  
  console.log(stdout);
);

Cuando ejecutamos el fragmento de código anterior, la salida se verá así:

tenedor:

La función fork es muy similar a la función spawn, la diferencia es que cuando se usa fork, el proceso hijo creado establecerá un canal IPC (comunicación entre procesos) con el proceso padre. Por lo tanto, podemos utilizar este método para casos de uso que requieren un canal de comunicación bidireccional entre el proceso padre y el proceso hijo.

Tengamos un archivo de temporizador de la siguiente manera:

//timer.js  
  
  let seconds = 0; 
  
  setInterval(()=> 
   process.send(seconds:`$++seconds form pid $process.pid`); 
  ,1000); 
  
  process.on('message',msg=> 
   console.log('from parent process',msg); 
  );

Ahora, usaremos el método fork para crear un proceso hijo y ejecutaremos el archivo timer.js en el proceso fork, como se muestra a continuación:

const  fork  = require('child_process'); 
  
const forkedProcess = fork('timer.js'); 
  
forkedProcess.on('message', (msg) =>  
  console.log('Message from child', msg); 
); 
  
forkedProcess.send( message: `This is from parent pid: $process.pid` )

El fragmento de código anterior demuestra el canal de IPC entre el proceso principal y el proceso secundario:

Comparar:

estándar desove implementar Archivo ejecutable tenedor
Cree un nuevo shell en la instanciación del proceso hijo De forma predeterminada, no se generará ningún shell para ejecutar el comando dado. De forma predeterminada, se generará un shell y el comando dado se ejecutará en él. De forma predeterminada, no se genera ningún shell para ejecutar comandos. No se producen conchas.
Opciones para generar el shell Puede crear un shell pasando la opción shell: true Puede evitar generar un shell pasando la opción shell: false Puede crear un shell pasando la opción shell: true Las opciones de Shell no son compatibles.
eficiente Dado que el comportamiento predeterminado no implica la generación de shell, se considera más eficiente que exec. El comportamiento predeterminado es menos eficiente porque necesita crear un shell antes de ejecutar un comando determinado. El comportamiento predeterminado es más eficiente que el método exec. No se trata de la generación de shell, pero la generación de una gran cantidad de procesos secundarios puede provocar el consumo de recursos, ya que el proceso generado tendrá su propia instancia v8.
comportamiento asincrónico asincrónico asincrónico asincrónico
Procesamiento de salida generado de comandos ejecutados Transmisión de salida La salida está almacenada en búfer La salida está almacenada en búfer Transmisión de salida
Versión sincronizada Generar sincronización () Realizar sincronización () execFileSync () no aplica

En conclusión:

Generalmente, si se trata de tareas que consumen muchos recursos y que pueden bloquear el hilo principal, desde una perspectiva de rendimiento, descargue la tarea a un proceso separado para asegurarse de que nuestro hilo principal no se detenga, evitando así que nuestro código bloquee el ciclo de eventos.

Si crea un nuevo proceso solo para expandir la cantidad de instancias en ejecución de la aplicación para atender el aumento del tráfico, verifique `Clúster` Los módulos ayudarán.

Si desea explorar métodos de subprocesos múltiples en lugar de métodos de procesamiento múltiple para manejar tareas computacionalmente intensivas en Node.js, debe consultar otro módulo llamado `worker_threads`.

Publicaciones relacionadas

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Botón volver arriba