One place for hosting & domains

      procesos

      Cómo usar ps, kill y nice para administrar procesos en Linux


      Introducción


      Un servidor Linux, como cualquier otro equipo con el que pueda estar familiarizado, ejecuta aplicaciones. Para el equipo, estos se consideran “procesos”.

      Mientras que Linux se encargará de la administración de bajo nivel, entre bastidores, en el ciclo de vida de un proceso, se necesitará una forma de interactuar con el sistema operativo para administrarlo desde un nivel superior.

      En esta guía, explicaremos algunos aspectos sencillos de la administración de procesos. Linux proporciona una amplia colección de herramientas para este propósito.

      Exploraremos estas ideas en un VPS de Ubuntu 12.04, pero cualquier distribución moderna de Linux funcionará de manera similar.

      Cómo ver los procesos en ejecución en Linux


      top


      La forma más sencilla de averiguar qué procesos se están ejecutando en su servidor es ejecutar el comando top:

      top***
      
      top - 15:14:40 up 46 min,  1 user,  load average: 0.00, 0.01, 0.05 Tasks:  56 total,   1 running,  55 sleeping,   0 stopped,   0 zombie Cpu(s):  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st Mem:   1019600k total,   316576k used,   703024k free,     7652k buffers Swap:        0k total,        0k used,        0k free,   258976k cached   PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND               1 root      20   0 24188 2120 1300 S  0.0  0.2   0:00.56 init                   2 root      20   0     0    0    0 S  0.0  0.0   0:00.00 kthreadd               3 root      20   0     0    0    0 S  0.0  0.0   0:00.07 ksoftirqd/0            6 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 migration/0            7 root      RT   0     0    0    0 S  0.0  0.0   0:00.03 watchdog/0             8 root       0 -20     0    0    0 S  0.0  0.0   0:00.00 cpuset                 9 root       0 -20     0    0    0 S  0.0  0.0   0:00.00 khelper               10 root      20   0     0    0    0 S  0.0  0.0   0:00.00 kdevtmpfs
      

      La parte superior de la información ofrece estadísticas del sistema, como la carga del sistema y el número total de tareas.

      Se puede ver fácilmente que hay 1 proceso en ejecución y 55 procesos en reposo (es decir, inactivos/que no usan los recursos de la CPU).

      La parte inferior tiene los procesos en ejecución y las estadísticas de uso.

      htop


      Una versión mejorada de top, llamada htop, está disponible en los repositorios. Instálelo con este comando:

      sudo apt-get install htop
      

      Si ejecutamos el comando htop, veremos que hay una pantalla más fácil de usar:

      htop***
      
        Mem[|||||||||||           49/995MB]     Load average: 0.00 0.03 0.05   CPU[                          0.0%]     Tasks: 21, 3 thr; 1 running   Swp[                         0/0MB]     Uptime: 00:58:11   PID USER      PRI  NI  VIRT   RES   SHR S CPU% MEM%   TIME+  Command  1259 root       20   0 25660  1880  1368 R  0.0  0.2  0:00.06 htop     1 root       20   0 24188  2120  1300 S  0.0  0.2  0:00.56 /sbin/init   311 root       20   0 17224   636   440 S  0.0  0.1  0:00.07 upstart-udev-brid   314 root       20   0 21592  1280   760 S  0.0  0.1  0:00.06 /sbin/udevd --dae   389 messagebu  20   0 23808   688   444 S  0.0  0.1  0:00.01 dbus-daemon --sys   407 syslog     20   0  243M  1404  1080 S  0.0  0.1  0:00.02 rsyslogd -c5   408 syslog     20   0  243M  1404  1080 S  0.0  0.1  0:00.00 rsyslogd -c5   409 syslog     20   0  243M  1404  1080 S  0.0  0.1  0:00.00 rsyslogd -c5   406 syslog     20   0  243M  1404  1080 S  0.0  0.1  0:00.04 rsyslogd -c5   553 root       20   0 15180   400   204 S  0.0  0.0  0:00.01 upstart-socket-br
      

      Puede aprender más sobre cómo usar top y htop aquí.

      Cómo usar ps para realizar una lista de procesos


      top y htop proporcionan una buena interfaz para ver los procesos en ejecución, similar a la de un administrador de tareas gráfico.

      Sin embargo, estas herramientas no siempre son lo suficientemente flexibles para cubrir adecuadamente todos los escenarios. Un poderoso comando llamado ps generalmente es la respuesta a estos problemas.

      Cuando se invoca sin argumentos, el resultado puede ser un poco escaso:

      ps***
      
        PID TTY          TIME CMD  1017 pts/0    00:00:00 bash  1262 pts/0    00:00:00 ps
      

      Este resultado muestra todos los procesos asociados con el usuario actual y la sesión de terminal. Eso tiene sentido porque, actualmente, solo estamos ejecutando bash y ps con este terminal.

      Para obtener un panorama más completo de los procesos en este sistema, podemos ejecutar lo siguiente:

      ps aux***
      
      USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND root         1  0.0  0.2  24188  2120 ?        Ss   14:28   0:00 /sbin/init root         2  0.0  0.0      0     0 ?        S    14:28   0:00 [kthreadd] root         3  0.0  0.0      0     0 ?        S    14:28   0:00 [ksoftirqd/0] root         6  0.0  0.0      0     0 ?        S    14:28   0:00 [migration/0] root         7  0.0  0.0      0     0 ?        S    14:28   0:00 [watchdog/0] root         8  0.0  0.0      0     0 ?        S<   14:28   0:00 [cpuset] root         9  0.0  0.0      0     0 ?        S<   14:28   0:00 [khelper] . . .
      

      Estas opciones ordenan a ps que muestre los procesos de propiedad de todos los usuarios (independientemente de su asociación con el terminal) en un formato fácil de usar.

      Para ver una vista de estructura jerárquica, en la que se ilustran las relaciones jerárquicas, podemos ejecutar el comando con las siguientes opciones:

      ps axjf***
      
       PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND     0     2     0     0 ?           -1 S        0   0:00 [kthreadd]     2     3     0     0 ?           -1 S        0   0:00  _ [ksoftirqd/0]     2     6     0     0 ?           -1 S        0   0:00  _ [migration/0]     2     7     0     0 ?           -1 S        0   0:00  _ [watchdog/0]     2     8     0     0 ?           -1 S<       0   0:00  _ [cpuset]     2     9     0     0 ?           -1 S<       0   0:00  _ [khelper]     2    10     0     0 ?           -1 S        0   0:00  _ [kdevtmpfs]     2    11     0     0 ?           -1 S<       0   0:00  _ [netns] . . .
      

      Como puede ver, el proceso kthreadd se muestra como proceso principal de kstadd/0 y los demás.

      Una nota sobre las ID de procesos


      En Linux y sistemas tipo Unix, a cada proceso se le asigna un ID de proceso o PID. Esta es la forma en que el sistema operativo identifica y realiza un seguimiento de los procesos.

      Una forma rápida de obtener el PID de un proceso es con el comando pgrep:

      pgrep bash***
      
      1017
      

      Esto simplemente consultará el ID del proceso y lo mostrará en el resultado.

      El primer proceso que se generó en el arranque, llamado init, recibe el PID “1”.

      pgrep init***
      
      1
      

      Entonces, este proceso es responsable de engendrar todos los demás procesos del sistema. Los procesos posteriores reciben números PID mayores.

      Un proceso principal es el proceso que se encargó de generarlo. Los procesos principales tienen un PPID, que puede ver en los encabezados de las columnas en muchas aplicaciones de administración de procesos, incluidos top, htop y ps.

      Cualquier comunicación entre el usuario y el sistema operativo sobre los procesos implica traducir entre los nombres de procesos y los PID en algún momento durante la operación. Este es el motivo por el que las utilidades le indican el PID.

      Las relaciones principal-secundario


      Para crear un proceso secundario se deben seguir dos pasos: fork(), que crea un nuevo espacio de direcciones y copia los recursos propiedad del principal mediante copy-on-write para que estén disponibles para el proceso secundario; y exec(), que carga un ejecutable en el espacio de direcciones y lo ejecuta.

      En caso de que un proceso secundario muera antes que su proceso principal, el proceso secundario se convierte en un zombi hasta que el principal haya recopilado información sobre él o haya indicado al núcleo que no necesita esa información. Luego, los recursos del proceso secundario se liberarán. Sin embargo, si el proceso principal muere antes que el secundario, init adoptará el secundario, aunque también puede reasignarse a otro proceso.

      Cómo enviar señales a los procesos en Linux


      Todos los procesos en Linux responden a señales. Las señales son una forma de decirle a los programas que terminen o modifiquen su comportamiento.

      Cómo enviar señales a los procesos por PID


      La forma más común de pasar señales a un programa es con el comando kill.

      Como es de esperar, la funcionalidad predeterminada de esta utilidad es intentar matar un proceso:

      kill PID_of_target_process

      Esto envía la señal TERM al proceso. La señal TERM indica al proceso debe terminar. Esto permite que el programa realice operaciones de limpieza y cierre sin problemas.

      Si el programa tiene un mal comportamiento y no se cierra cuando se le da la señal TERM, podemos escalar la señal pasando la señal KILL:

      kill -KILL PID_of_target_process

      Esta es una señal especial que no se envía al programa.

      En su lugar, se envía al núcleo del sistema operativo, que cierra el proceso. Eso se utiliza para eludir los programas que ignoran las señales que se les envían.

      Cada señal tiene un número asociado que puede pasar en vez del nombre. Por ejemplo, puede pasar “-15” en lugar de “-TERM” y “-9” en lugar de “-KILL”.

      Cómo usar señales para otros fines


      Las señales no solo se utilizan para cerrar programas. También pueden usarse para realizar otras acciones.

      Por ejemplo, muchos demonios se reinician cuando reciben la señal HUP o la señal de colgar. Apache es un programa que funciona así.

      sudo kill -HUP pid_of_apache

      El comando anterior hará que Apache vuelva a cargar su archivo de configuración y reanude sirviendo contenidos.

      Puede enumerar todas las señales que puede enviar con kill escribiendo lo siguiente:

      kill -l***
      
      1) SIGHUP    2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP  6) SIGABRT  7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM . . .
      

      Cómo enviar señales a los procesos por nombre


      Aunque la forma convencional de enviar señales es mediante el uso de PID, también hay métodos para hacerlo con nombres de procesos regulares.

      El comando pkill funciona casi exactamente igual que kill, pero funciona con un nombre de proceso en su lugar:

      pkill -9 ping
      

      El comando anterior es el equivalente a:

      kill -9 `pgrep ping`
      

      Si quiere enviar una señal a todas las instancias de un determinado proceso, puede utilizar el comando killall:

      killall firefox
      

      El comando anterior enviará la señal TERM a todas las instancias de firefox que se estén ejecutando en el equipo.

      Cómo ajustar las prioridades de los procesos


      A menudo, querrá ajustar qué procesos reciben prioridad en un entorno de servidor.

      Algunos procesos pueden considerarse como una misión crítica para su situación, mientras que otros pueden ejecutarse siempre que haya recursos sobrantes.

      Linux controla la prioridad a través de un valor llamado niceness.

      Las tareas de alta prioridad se consideran menos buenas porque no comparten los recursos tan bien. Por otro lado, los procesos de baja prioridad son buenos porque insisten en tomar solo los recursos mínimos.

      Cuando ejecutamos top al principio del artículo, había una columna marcada como “NI”. Este es el valor bueno del proceso:

      top***
      
      Tasks: 56 total, 1 running, 55 sleeping, 0 stopped, 0 zombie Cpu(s):  0.0%us,  0.3%sy,  0.0%ni, 99.7%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st Mem:   1019600k total,   324496k used,   695104k free,     8512k buffers Swap:        0k total,        0k used,        0k free,   264812k cached   PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND            1635 root      20   0 17300 1200  920 R  0.3  0.1   0:00.01 top                    1 root      20   0 24188 2120 1300 S  0.0  0.2   0:00.56 init                   2 root      20   0     0    0    0 S  0.0  0.0   0:00.00 kthreadd               3 root      20   0     0    0    0 S  0.0  0.0   0:00.11 ksoftirqd/0
      

      Los valores buenos pueden oscilar entre “-19/-20” (máxima prioridad) y “19/20” (mínima prioridad) dependiendo del sistema.

      Para ejecutar un programa con un determinado valor bueno, podemos usar el comando nice:

      nice -n 15 command_to_execute

      Esto solo funciona cuando se inicia un nuevo programa.

      Para alterar el valor bueno de un programa que ya se está ejecutando, usamos una herramienta llamada renice:

      renice 0 PID_to_prioritize

      Nota: Mientras que nice funciona necesariamente con un nombre de comando, renice funciona invocando al PID del proceso

      Conclusión


      La administración de procesos es un tema que a veces resulta difícil de entender para los nuevos usuarios, debido a que las herramientas utilizadas son diferentes a sus contrapartes gráficas.

      Sin embargo, las ideas son familiares e intuitivas, y, con un poco de práctica, se convertirá en algo natural. Dado que los procesos interviene en todo lo que se hace con un sistema informático, aprender a controlarlos de forma eficaz es una habilidad esencial.

      Por Justin Ellingwood



      Source link

      Cómo iniciar procesos secundarios en Node.js


      El autor seleccionó el COVID-19 Relief Fund para que reciba una donación como parte del programa Write for DOnations.

      Introducción

      Cuando un usuario ejecuta un único programa Node.js, lo ejecuta como un proceso de sistema operativo (SO) individual que representa la instancia del programa en ejecución. En ese proceso, Node.js ejecuta programas en un único hilo. Como se ha mencionado anteriormente en esta serie con el tutorial Cómo escribir código asíncrono en Node.js, debido a que solo un hilo puede ejecutarse sobre un proceso, las operaciones que tardan mucho tiempo en ejecutarse en JavaScript pueden bloquear el hilo de Node.js y retrasar la ejecución de otro código. Una estrategia clave para resolver este problema es iniciar un proceso secundario, o un proceso creado por otro proceso cuando nos enfrentamos a tareas de larga ejecución. Cuando se inicia un nuevo proceso, el sistema operativo puede emplear técnicas de multi procesamiento para garantizar que el proceso Node.js principal y el proceso secundario adicional se ejecutan de forma simultánea o al mismo tiempo.

      Node.js incluye el módulo child_process que tiene funciones para crear nuevos procesos. Aparte de tratar con tareas de larga duración, este módulo también interactúa con el SO y ejecuta comandos shell. Los administradores de sistemas pueden usar Node.js para ejecutar comandos shell para estructurar y mantener sus operaciones como un módulo Node.js en vez de como secuencias de comando shell.

      En este tutorial, creará procesos secundarios mientras ejecuta una serie de aplicaciones Node.js de muestra. Creará procesos con el módulo child_process recuperando los resultados de un proceso secundario a través de un búfer o cadena con la función exec(), y a continuación desde un flujo de datos con la función spawn(). Terminará usando fork() para crear un proceso secundario de otro programa Node.js con el que puede comunicarse cuando se ejecute. Para ilustrar estos conceptos, escribirá un programa para enumerar el contenido de un directorio, un programa para buscar archivos y un servidor web con múltiples endpoints.

      Requisitos previos

      Paso 1: Crear un proceso secundario con exec()

      Los desarrolladores crean de forma habitual procesos secundarios para ejecutar comandos sobre su sistema operativo cuando necesitan manipular el resultado de sus programas Node.js con un shell, como usar una canalización o redireccionamiento shell. La función exec() en Node.js crea un nuevo proceso shell y ejecuta un comando en ese shell. El resultado del comando se mantiene en un búfer en la memoria, que puede aceptar a través de una función callback pasada a exec().

      Vamos a comenzar a crear nuestros primeros procesos secundarios en Node.js. Primero, debemos configurar nuestro entornos de codificación para almacenar las secuencias de comandos que crearemos durante este tutorial. En el terminal, cree una carpeta llamada child-processes:

      Introduzca esa carpeta en el terminal con el comando cd:

      Cree un nuevo archivo llamado listFiles.js y abra el archivo en un editor de texto. En este tutorial usaremos nano, un editor de texto terminal:

      Escribiremos un módulo Node.js que utiliza la función exec() para ejecutar el comando ls. El comando ls enumera los archivos y las carpetas en un directorio. Este programa toma el resultado del comando ls y lo muestra al usuario.

      En el editor de texto, añada el siguiente código:

      ~/child-processes/listFiles.js

      const { exec } = require('child_process');
      
      exec('ls -lh', (error, stdout, stderr) => {
        if (error) {
          console.error(`error: ${error.message}`);
          return;
        }
      
        if (stderr) {
          console.error(`stderr: ${stderr}`);
          return;
        }
      
        console.log(`stdout:n${stdout}`);
      });
      

      Primero, importamos el comando exec() desde el módulo child_process usando JavaScript destructuring. Una vez importado, usamos la función exec(). El primer argumento es el comando que nos gustaría ejecutar. En este caso, es ls -lh, que enumera todos los archivos y carpetas del directorio actual en formato largo, con un tamaño total de archivo en unidades legibles por el ser humano en la parte superior del resultado.

      El segundo argumento es una función callback con tres parámetros: error, stdout y stderr. Si el comando no se ejecuta, error capturará el motivo por el que falló. Es posible que esto suceda si el shell no puede encontrar el comando que está intentando ejecutar. Si el comando se ejecutó correctamente, cualquier dato que escriba al flujo de resultado estándar se captura en stdout y cualquier dato que escriba al flujo error estándar se captura en stderr.

      Nota: Es importante tener en cuenta la diferencia entre error y stderr. Si el comando en sí no se ejecuta, error capturará el error. Si el comando se ejecuta, pero devuelve el resultado al flujo de error, stderr lo capturará. Los programas más resilientes de Node.js gestionarán todos los resultados posibles para un proceso secundario.

      En nuestra función callback, primero comprobaremos si recibimos un error. Si lo hicimos, mostramos el message (mensaje) del error (una propiedad del objeto Error) con console.error() y finalizamos la función con return. A continuación, comprobamos si el comando imprimió un mensaje de error y return si es así. Si el comando se ejecuta correctamente, registramos su resultado a la consola con console.log().

      Vamos a ejecutar este archivo para verlo en acción. Primero, guarde y salga de nano pulsando CTRL+X.

      De vuelta en su terminal, ejecute su aplicación con el comando node:

      Su terminal mostrará el siguiente resultado:

      Output

      stdout: total 4.0K -rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js

      Esto enumera el contenido del directorio child-processes en formato largo, junto con el tamaño del contenido en la parte superior. Sus resultados tendrán su propio usuario y grupo en lugar de sammy. Esto muestra que el programa listFiles.js ejecutó correctamente el comando shell ls -lh.

      Ahora, vamos a ver otra forma de ejecutar procesos simultáneos. El módulo child_process de Node.js puede ejecutar también archivos ejecutables con la función execFile(). La diferencia principal entre las funciones execFile() y exec() es que el primer argumento de execFile() es ahora una ruta a un archivo ejecutable en vez de un comando. El resultado del archivo ejecutable se guarda en un búfer como exec(), al que accedemos a través de una función callback con los parámetros error, stdout y stderr.

      Nota: Las secuencias de comandos en Windows como archivos .bat y .cmd no se pueden ejecutar con execFile() porque la función no crea un shell cuando se ejecuta el archivo. En Unix, Linux y macOS las secuencias de comandos ejecutables no siempre necesitan un shell para ejecutarse. Sin embargo, un equipo Windows necesita un shell para ejecutar secuencias de comandos. Para ejecutar archivos de secuencias de comandos en Windows, utilice exec(), ya que crea un nuevo shell. Alternativamente, puede usar spawn(), que usará más tarde en este Paso.

      Sin embargo, observe que puede ejecutar archivos .exe en Windows correctamente usando execFile(). Esta limitación solo se aplica a archivos de secuencias de comandos que requieren un shell para ejecutarse.

      Vamos a comenzar añadiendo una secuencia de comandos ejecutable para que execFile() se ejecute. Escribiremos una secuencia de comandos bash que descarga el logotipo de Node.js desde el sitio web de Node.js, y Base64 lo codifica para convertir sus datos en una cadena de caracteres ASCII.

      Cree un nuevo archivo de secuencia de comando shell llamado processNodejsImage.sh:

      • nano processNodejsImage.sh

      Ahora escriba una secuencia de comandos para descargar la imagen y base64 para convertirla:

      ~/child-processes/processNodejsImage.sh

      #!/bin/bash
      curl -s https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg > nodejs-logo.svg
      base64 nodejs-logo.svg
      

      La primera instrucción es una instrucción shebang. Se usa en Unix, Linux y macOs cuando queremos especificar un shell para que ejecute nuestra secuencia de comandos. La segunda instrucción es un comando curl. La utilidad cURL, cuyo comando es curl, es una herramienta de línea de comandos que puede transferir datos a y desde un servidor. Usamos cURL para descargar el logotipo de Node.js desde el sitio web, y luego usamos redirection para guardar los datos descargados a un nuevo archivo nodejs-logo.svg. La última instrucción utiliza la utilidad base64 para codificar el archivo nodejs-logo.svg que descargamos con cURL. La secuencia de comandos da como resultado la cadena codificada en la consola.

      Guarde y salga antes de continuar.

      Para que nuestro programa Node ejecute la secuencia de comandos bash, tenemos que hacer que sea ejecutable. Para hacer esto, ejecute lo siguiente:

      • chmod u+x processNodejsImage.sh

      Esto le dará a su usuario actual el permiso para ejecutar el archivo.

      Con nuestra secuencia de comandos en su lugar, podemos escribir un nuevo módulo Node.js para ejecutarlo. Esta secuencia de comandos usará execFile() para ejecutar la secuencia de comandos en un proceso secundario, detectando cualquier error y mostrando todos los resultados en la consola.

      En su terminal, cree un nuevo archivo JavaScript llamado getNodejsImage.js:

      Escriba el siguiente código en el editor de texto:

      ~/child-processes/getNodejsImage.js

      const { execFile } = require('child_process');
      
      execFile(__dirname + '/processNodejsImage.sh', (error, stdout, stderr) => {
        if (error) {
          console.error(`error: ${error.message}`);
          return;
        }
      
        if (stderr) {
          console.error(`stderr: ${stderr}`);
          return;
        }
      
        console.log(`stdout:n${stdout}`);
      });
      

      Usaremos la desestructuración de JavaScript para importar la función execFile() desde el módulo child_process. Luego usamos esa función, pasando la ruta de archivo como el primero nombre. __dirname contiene la ruta del directorio del módulo en el que está escrito. Node.js proporciona la variable __dirname a un módulo cuando el módulo se ejecuta. Al usar __dirname, nuestra secuencia de comandos siempre encontrará el archivo processNodejsImage.sh en diferentes sistemas operativos, sin importar dónde ejecutemos getNodejsImage.js. Tenga en cuenta que para nuestra configuración actual del proyecto, getNodejsImage.js y processNodejsImage.sh deben estar en la misma carpeta.

      El segundo argumento es un callback con los parámetros error, stdout y stderr. Igual que en nuestro ejemplo anterior que usó exec(), comprobamos cada resultado posible del archivo de la secuencia de comandos y los registraremos en la consola.

      En su editor de texto, guarde este archivo y salga del editor.

      En su terminal, utilice node para ejecutar el módulo:

      Ejecutar la secuencia de comandos producirá un resultado similar a este:

      Output

      stdout: PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDQyLjQgMjcwLjkiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjE4MC43IiB5MT0iODAuNyIge ...

      Tenga en cuenta que hemos recortado el resultado en este artículo debido a su gran tamaño.

      Antes de que base64 codifique la imagen, processNodejsImage.sh primero la descarga. Puede también verificar que descargó la imagen inspeccionando el directorio actual.

      Ejecute listFiles.js para buscar la lista actualizada de archivos en nuestro directorio:

      La secuencia de comandos mostrará contenido similar al siguiente en el terminal:

      Output

      stdout: total 20K -rw-rw-r-- 1 sammy sammy 316 Jul 27 17:56 getNodejsImage.js -rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js -rw-rw-r-- 1 sammy sammy 5.4K Jul 27 18:01 nodejs-logo.svg -rwxrw-r-- 1 sammy sammy 129 Jul 27 17:56 processNodejsImage.sh

      Ahora hemos ejecutado correctamente processNodejsImage.sh como proceso secundario en Node.js usando la función execFile().

      Las funciones exec() y execFile() pueden ejecutar comandos sobre el shell del sistema operativo en un proceso secundario Node.js. Node.js también proporciona otro método con funcionalidad similar, spawn(). La diferencia es que en vez de obtener el resultado de los comandos shell a la vez, los obtendremos en bloques a través de un flujo. En la siguiente sección usaremos el comando spawn() para crear un proceso secundario.

      Paso 2: Crear un proceso secundario con spawn()

      La función spawn() ejecuta un comando en un proceso. Esta función devuelve datos a través de la API del flujo. Por tanto, para obtener el resultado del proceso secundario, debemos escuchar los eventos del flujo.

      Los flujos en Node.js son instancias de los emisores de eventos. Si desea obtener más información sobre la escucha de eventos y los fundamentos de interacción con los flujos, puede leer nuestra guía sobre Usar emisores de eventos en Node.js.

      A menudo es una buena idea seleccionar spawn() en vez de exec() o execFile() cuando el comando que desea ejecutar puede dar como resultado una gran cantidad de datos. Con un búfer, como utilizan exec() y execFile(), todos los datos procesados se guardan en la memoria de la computadora. Para cantidades de datos más grandes, esto puede degradar el rendimiento del sistema. Con un flujo, los datos se procesan y transfieren en pequeños trozos. Por tanto, puede procesar una gran cantidad de datos sin usar demasiada memoria en un momento dado.

      Vamos a ver cómo podemos usar spawn() para crear un proceso secundario. Escribiremos un nuevo módulo Node.js que crea un proceso secundario para ejecutar el comando find. Usaremos el comando find para enumerar todos los archivos en el directorio actual.

      Cree un nuevo archivo llamado findFiles.js:

      En su editor de texto, comience invocando el comando spawn():

      ~/child-processes/findFiles.js

      const { spawn } = require('child_process');
      
      const child = spawn('find', ['.']);
      

      Primero importamos la función spawn() desde el módulo child_process. A continuación invocamos la función spawn() para crear un proceso secundario que ejecuta el comando find. Albergamos la referencia al proceso en la variable child, que utilizaremos para escuchar sus eventos transmitidos.

      El primer argumento en spawn() es el comando a ejecutar, en este caso find. El segundo argumento es un array que contiene los argumentos para el comando ejecutado. En este caso, le estamos indicando a Node.js que ejecute el comando find con el argumento ., lo que hace que el comando encuentre todos los activos en el directorio actual. El comando equivalente en el terminal es find .

      Con las funciones exec() y execFile(), escribimos los argumentos con el comando en una cadena. Sin embargo, con spawn(), todos los argumentos para los comandos deben introducirse en el array. Eso es porque spawn(), a diferencia de exec() y execFile(), no crea un nuevo shell antes de ejecutar un proceso. Para tener los comandos con sus argumentos en una cadena, necesita que Node.js cree un nuevo shell también.

      Vamos a continuar nuestro módulo añadiendo oyentes para el resultado del comando. Añada las siguientes líneas resaltadas:

      ~/child-processes/findFiles.js

      const { spawn } = require('child_process');
      
      const child = spawn('find', ['.']);
      
      child.stdout.on('data', data => {
        console.log(`stdout:n${data}`);
      });
      
      child.stderr.on('data', data => {
        console.error(`stderr: ${data}`);
      });
      

      Los comandos pueden devolver datos en el flujo stdout o el flujo stderr, de forma que añadió oyentes para ambos. Puede añadir oyentes invocando el método on() de cada objeto de los flujos. El evento datos de los flujos nos proporciona el resultado de los comandos para ese flujo. Siempre que obtengamos datos sobre ese flujo, los registramos en la consola.

      A continuación escuchamos los dos otros eventos: el evento error si el comando no se ejecuta o se interrumpe y el evento close para cuando el comando haya terminado la ejecución, cerrando así el flujo.

      En el editor de texto, complete el módulo Node.js escribiendo las siguientes líneas resaltadas:

      ~/child-processes/findFiles.js

      const { spawn } = require('child_process');
      
      const child = spawn('find', ['.']);
      
      child.stdout.on('data', (data) => {
        console.log(`stdout:n${data}`);
      });
      
      child.stderr.on('data', (data) => {
        console.error(`stderr: ${data}`);
      });
      
      child.on('error', (error) => {
        console.error(`error: ${error.message}`);
      });
      
      child.on('close', (code) => {
        console.log(`child process exited with code ${code}`);
      });
      

      Para los eventos error y close, configura un oyente directamente en la variable child. Cuando escucha eventos error, si se produce uno, Node.js ofrece un objeto Error. En este caso, registra la propiedad message del error.

      Cuando se escucha el evento close, Node.js proporciona el código de salida del comando. Un código de salida indica si el comando se ejecutó correctamente o no. Cuando un comando se ejecuta sin errores, devuelve el valor más bajo posible para un código de salida: 0. Cuando se ejecuta con un error, devuelve un código no cero.

      El módulo se ha completado. Guarde y salga de nano con CTRL+X.

      Ahora ejecute el código con el comando node:

      Una vez completado, encontrará el siguiente resultado:

      Output

      stdout: . ./findFiles.js ./listFiles.js ./nodejs-logo.svg ./processNodejsImage.sh ./getNodejsImage.js child process exited with code 0

      Encontramos una lista de todos los archivos en nuestro directorio actual y el código de salida del comando, que es 0 ya que se ejecutó correctamente. Aunque nuestro directorio actual tiene un número pequeño de archivos, si ejecutamos este código en nuestro directorio de inicio, nuestro programa listará cada archivo único en una carpeta accesible para nuestro usuario. Debido a que tiene un resultado potencialmente grande, usar la función spawn() es lo más ideal porque los flujos no requieren tanta memoria como un búfer grande.

      Hasta ahora hemos usado funciones para crear procesos secundarios para ejecutar comandos externos en nuestro sistema operativo. Node.js también ofrece una forma de crear un proceso secundario que ejecuta otros programas Node.js. Vamos a usar la función fork() para crear un proceso secundario para un módulo Node.js en la siguiente sección.

      Paso 3: Crear un proceso secundario con fork()

      Node.js proporciona la función fork(), una variación de spawn(), para crear un proceso secundario que es también un proceso Node.js. El principal beneficio de usar fork() para crear un proceso Node.js en comparación con spawn() o exec() es que fork() permite la comunicación entre el proceso principal y el secundario.

      Con fork(), además de recuperar datos desde el proceso secundario, un proceso principal puede enviar mensajes al proceso secundario en ejecución. Del mismo modo, el proceso secundario puede enviar mensajes al proceso principal.

      Vamos a ver un ejemplo en el que usar fork() para crear un nuevo proceso secundario de Node.js puede mejorar el rendimiento de nuestra aplicación. Los programas Node.js se ejecutan sobre un proceso único. Por tanto, las tareas intensivas para la CPU, como la repetición sobre grandes bucles o analizar grandes archivos JSON impiden que otros códigos JavaScript se ejecuten. Para ciertas aplicaciones, esta no es una opción viable. Si un servidor web está bloqueado, no puede procesar ninguna nueva solicitud entrante hasta que el código de bloqueo haya completado su ejecución.

      Vamos a ver esto en la práctica creando un servidor web con dos endpoints. Un endpoint realizará una computación lenta que bloquea el proceso Node.js. El otro endpoint devolverá un objeto JSON que dice hello.

      Primero, cree un nuevo archivo llamado httpServer.js que tendrá el código para nuestro servidor HTTP:

      Empezaremos configurando el servidor HTTP. Esto implica importar el módulo http, creando una función oyente de la solicitud, creando un objeto de servidor y escuchando las solicitudes sobre el objeto del servidor. Si desea profundizar en la creación de servidores HTTP en Node.js o desea repasar estos conceptos, puede leer nuestra guía sobre Cómo crear un servidor web en Node.js con el módulo HTTP.

      Introduzca el siguiente código en su editor de texto para configurar un servidor HTTP:

      ~/child-processes/httpServer.js

      const http = require('http');
      
      const host="localhost";
      const port = 8000;
      
      const requestListener = function (req, res) {};
      
      const server = http.createServer(requestListener);
      server.listen(port, host, () => {
        console.log(`Server is running on http://${host}:${port}`);
      });
      

      Este código configura un servidor HTTP que se ejecutará en http://localhost:8000. Utiliza literales de plantilla para generar dinámicamente esa URL.

      A continuación, escribiremos una función intencionadamente lenta que cuenta en un bucle 5 mil millones de veces. Antes de la función requestListener(), añada el siguiente código:

      ~/child-processes/httpServer.js

      ...
      const port = 8000;
      
      const slowFunction = () => {
        let counter = 0;
        while (counter < 5000000000) {
          counter++;
        }
      
        return counter;
      }
      
      const requestListener = function (req, res) {};
      ...
      

      Esto utiliza la sintaxis de la función de flecha para crear un bucle while que cuenta hasta 5000000000.

      Para completar este módulo, debemos añadir código a la función requestListener(). Nuestra función invocará slowFunction() en una subruta, y devolverá un mensaje JSON pequeño para la otra. Añada el siguiente código al módulo:

      ~/child-processes/httpServer.js

      ...
      const requestListener = function (req, res) {
        if (req.url === '/total') {
          let slowResult = slowFunction();
          let message = `{"totalCount":${slowResult}}`;
      
          console.log('Returning /total results');
          res.setHeader('Content-Type', 'application/json');
          res.writeHead(200);
          res.end(message);
        } else if (req.url === '/hello') {
          console.log('Returning /hello results');
          res.setHeader('Content-Type', 'application/json');
          res.writeHead(200);
          res.end(`{"message":"hello"}`);
        }
      };
      ...
      

      Si el usuario llega al servidor en la subruta /total, ejecutaremos slowFunction(). Si llegamos a la subruta /hello, devolveremos este mensaje JSON: {"message":"hello"}.

      Guarde y salga del archivo pulsando CTRL+X.

      Como prueba, ejecute este módulo del servidor con node:

      Cuando se inicie nuestro servidor, la consola mostrará lo siguiente:

      Output

      Server is running on http://localhost:8000

      Ahora, para probar el rendimiento de nuestro módulo, abra dos terminales adicionales. En el primer terminal, utilice el comando curl para realizar una solicitud al endpoint /total, que esperamos que sea lento:

      • curl http://localhost:8000/total

      En el otro terminal, utilice curl para realizar una solicitud al endpoint /hello, de esta forma:

      • curl http://localhost:8000/hello

      La primera solicitud devolverá el siguiente JSON:

      Output

      {"totalCount":5000000000}

      Mientras que la segunda solicitud devolverá este JSON:

      Output

      {"message":"hello"}

      La solicitud para /hello se completó solo tras la solicitud para /total. slowFunction() impidió que el resto de códigos se ejecutasen mientras aún estaba en su bucle. Puede verificar esto observando el resultado del servidor Node.js que se registró en su terminal original:

      Output

      Returning /total results Returning /hello results

      Para procesar el código de bloqueo mientras aún acepta solicitudes entrantes, podemos mover el código de bloqueo a un proceso secundario con fork(). Moveremos el código de bloqueo a su propio módulo. El servidor Node.js creará un proceso secundario cuando alguien acceda al endpoint /total y escuche los resultados de este proceso secundario.

      Refactorice el servidor creando primero un nuevo módulo llamado getCount.js que contendrá slowFunction():

      Ahora introduzca el código para slowFunction() de nuevo:

      ~/child-processes/getCount.js

      const slowFunction = () => {
        let counter = 0;
        while (counter < 5000000000) {
          counter++;
        }
      
        return counter;
      }
      

      Ya que este módulo será un proceso secundario creado con fork(), podemos añadir además código para que se comunique con el proceso principal cuando slowFunction() haya completado el procesamiento. Añada el siguiente bloque de código que envía un mensaje al proceso principal con el JSON que se devolverá al usuario:

      ~/child-processes/getCount.js

      const slowFunction = () => {
        let counter = 0;
        while (counter < 5000000000) {
          counter++;
        }
      
        return counter;
      }
      
      process.on('message', (message) => {
        if (message == 'START') {
          console.log('Child process received START message');
          let slowResult = slowFunction();
          let message = `{"totalCount":${slowResult}}`;
          process.send(message);
        }
      });
      

      Vamos a descomponer este bloque de código. Los mensajes entre un proceso principal y uno secundario creados por fork() están accesibles a través del objeto process global de Node.js. Añadimos un oyente a la variable process para que busque eventos message. Una vez que recibamos un evento message, comprobaremos si es el evento START. Nuestro código de servidor enviará el evento START cuando alguien acceda al endpoint /total. Tras recibir ese evento, ejecutaremos slowFunction() y crearemos una cadena JSON con el resultado de la función. Usaremos process.send() para enviar un mensaje al proceso principal.

      Guarde y salga de getCount.js introduciendo CTRL+X en nano.

      Ahora, vamos a modificar el archivo httpServer.js de forma que, en vez de invocar slowFunction(), cree un proceso secundario que ejecute getCount.js.

      Vuelva a abrir httpServer.js con nano:

      Primero, importe la función fork() desde el módulo child_process:

      ~/child-processes/httpServer.js

      const http = require('http');
      const { fork } = require('child_process');
      ...
      

      A continuación, eliminaremos la función slowFunction() de este módulo y modificaremos la función requestListener() para crear un proceso secundario. Cambie el código en su archivo de forma que tenga este aspecto:

      ~/child-processes/httpServer.js

      ...
      const port = 8000;
      
      const requestListener = function (req, res) {
        if (req.url === '/total') {
          const child = fork(__dirname + '/getCount');
      
          child.on('message', (message) => {
            console.log('Returning /total results');
            res.setHeader('Content-Type', 'application/json');
            res.writeHead(200);
            res.end(message);
          });
      
          child.send('START');
        } else if (req.url === '/hello') {
          console.log('Returning /hello results');
          res.setHeader('Content-Type', 'application/json');
          res.writeHead(200);
          res.end(`{"message":"hello"}`);
        }
      };
      ...
      

      Cuando alguien vaya al endpoint /total, crearemos un nuevo proceso secundario con fork(). El argumento de fork() es la ruta hacia el módulo Node.js. En este caso, es el archivo getCount.js de nuestro directorio actual, que recibimos de __dirname. La referencia a este proceso secundario se almacena en una variable child.

      A continuación, añadiremos un oyente al objeto child. Este oyente captura cualquier mensaje que el proceso secundario nos proporcione. En este caso, getCount.js devolverá una cadena JSON con la cantidad total contada por el bucle while. Cuando recibamos ese mensaje, enviaremos el JSON al usuario.

      Usaremos la función send() de la variable child para proporcionarle un mensaje. Este programa envía el mensaje START, que inicia la ejecución de slowFunction() en el proceso secundario.

      Guarde y salga de nano introduciendo CTRL+X.

      Para probar la mejora que el uso de fork() ha realizado sobre el servidor HTTP, comience ejecutando el archivo httpServer.js con node:

      Al igual que antes, dará como resultado el siguiente mensaje cuando se inicie:

      Output

      Server is running on http://localhost:8000

      Para probar el servidor, necesitaremos dos terminales adicionales como hicimos la primera vez. Puede reutilizarlas si aún están abiertas.

      En el primer terminal, utilice el comando curl para realizar una solicitud al endpoint /total, que tardará un poco en computarse:

      • curl http://localhost:8000/total

      En el otro terminal, utilice curl para realizar una solicitud al endpoint /hello, que responde en un breve periodo de tiempo:

      • curl http://localhost:8000/hello

      La primera solicitud devolverá el siguiente JSON:

      Output

      {"totalCount":5000000000}

      Mientras que la segunda solicitud devolverá este JSON:

      Output

      {"message":"hello"}

      A diferencia de la primera vez que probamos esto, la segunda solicitud a /hello se ejecuta de inmediato. Puede confirmar revisando los registros, que tendrán este aspecto:

      Output

      Child process received START message Returning /hello results Returning /total results

      Estos registros muestran que la solicitud para el endpoint /hello se ejecutaron después de haberse creado el proceso secundario pero antes de que el proceso secundario hubiese terminado su tarea.

      Ya que movimos el código de bloqueo de un proceso secundario usando fork(), el servidor pudo responder a otras solicitudes y ejecutar otros códigos JavaScript. Debido a la capacidad de transmisión de mensaje de la función fork(), podemos controlar cuándo un proceso secundario inicia una actividad y podemos devolver datos desde un proceso secundario a un proceso principal.

      Conclusión

      En este artículo, ha utilizado varias funciones para crear un proceso secundario en Node.js. Primero, creó procesos secundarios con exec() para ejecutar comandos shell desde el código Node.js. Luego, ejecutó un archivo ejecutable con la función execFile(). Ha visto la función spawn(), que también puede ejecutar comandos, pero devuelve datos a través de un flujo y no inicia un shell como exec() y execFile(). Finalmente, utilizó la función fork() para permitir comunicación bidireccional entre el proceso principal y secundario.

      Para obtener más información sobre el módulo child_process, puede leer la documentación de Node.js. Si desea continuar aprendiendo sobre Node.js, puede regresar a la serie Cómo programar en Node.js o consultar proyectos de programación y configuraciones en nuestra página temática de Node.



      Source link