Todos sabemos que Node.js utiliza un modelo de E/S asíncrono controlado por eventos de un solo subproceso. Sus características determinan que no puede aprovechar la CPU de múltiples núcleos y no es bueno para completar algunas operaciones que no son de E/S (. como ejecutar scripts), computación de IA, procesamiento de imágenes, etc.), para resolver tales problemas, Node.js proporciona una solución multiproceso (subprocesos) convencional (para discusiones sobre procesos e subprocesos, consulte la del autor). Otro artículo sobre Node.js y el modelo de concurrencia), este artículo le presentará el mecanismo multiproceso de Node.js.
Podemos usar el módulo child_process
para crear un proceso hijo de Node.js para completar algunas tareas especiales (como ejecutar scripts). Este módulo proporciona principalmente exec
, execFile
, fork
, spwan
y otros métodos. . usar.
const { ejecutivo } = require('child_process'); ejecutivo('ls -al', (error, salida estándar, stderr) => { console.log(salida estándar); });
este método procesa la cadena de comando de acuerdo con el archivo ejecutable especificado por options.shell
, almacena en caché su salida durante la ejecución del comando y luego devuelve el resultado de la ejecución en forma de parámetros de función de devolución de llamada hasta que se completa la ejecución del comando.
Los parámetros de este método se explican a continuación:
command
: el comando que se ejecutará (como ls -al
options
: configuración de parámetros (opcional), las propiedades relevantes son las siguientes:
cwd
: el directorio de trabajo actual del proceso hijo); , el valor predeterminado es process.cwd()
valor;
env
: configuración de variable de entorno (objeto de par clave-valor), el valor predeterminado es el valor de process.env
encoding
: codificación de caracteres, el valor predeterminado es: utf8
shell
: ejecutable
archivo que procesa cadenas de comandos, el valor predeterminado en Unix
es /bin/sh
, el valor predeterminado en Windows
es el valor de process.env.ComSpec
(si está vacío, es cmd.exe
, por ejemplo:
const { exec);
} = requerir('child_process'); exec("print('¡Hola mundo!')", { shell: 'python' }, (error, stdout, stderr) => { console.log(salida estándar); });
Al ejecutar el ejemplo anterior, se generará Hello World!
que es equivalente al subproceso que ejecuta python -c "print('Hello World!')"
. Por lo tanto, al usar este atributo, debe prestar atención al. archivo ejecutable especificado. Se debe admitir la ejecución de declaraciones relacionadas a través de la opción -c
.
Nota: Sucede que Node.js
también admite la opción -c
, pero es equivalente a la opción --check
. Solo se usa para detectar si hay errores de sintaxis en el script especificado y no ejecutará el script relevante.
signal
: utilice la AbortSignal especificada para finalizar el proceso hijo. Este atributo está disponible en la versión 14.17.0, por ejemplo:
const { exec } = require('child_process'); const ac = nuevo AbortController(); exec('ls -al', { signal: ac.signal }, (error, stdout, stderr) => {});
En el ejemplo anterior, podemos finalizar el proceso secundario antes de tiempo llamando ac.abort()
.
timeout
: el tiempo de espera del proceso hijo (si el valor de este atributo es mayor que 0
, cuando el tiempo de ejecución del proceso hijo exceda el valor especificado, la señal de terminación especificada por el atributo killSignal
se enviará al proceso hijo ), en milímetros, el valor predeterminado es 0
;
maxBuffer
: el caché máximo (binario) permitido por 1024 * 1024
o stderr. Si se excede, el proceso secundario se eliminará y cualquier salida se truncará
killSignal
: La señal de terminación del proceso secundario, el valor predeterminado es SIGTERM
;
uid
: uid
para ejecutar el proceso secundario;
gid
: gid
para ejecutar el proceso secundario
windowsHide
: si se debe ocultar la ventana de la consola del proceso secundario, comúnmente utilizada en sistemas Windows
; el valor predeterminado es false
;
callback
: función de devolución de llamada, que incluye error
, stdout
, stderr
Parámetros:
error
: si la línea de comando se ejecuta correctamente, el valor es null
; de lo contrario, el valor es una instancia de Error, donde error.code
es la salida. código de error del proceso hijo, error.signal
es la señal para la terminación del proceso hijo;stdout
y stderr
: child stdout
y stderr
del proceso se codifican de acuerdo con el valor del atributo encoding
si encoding
es buffer
. , o el valor de stdout
o stderr
es una cadena irreconocible, se codificará según buffer
.const { execFile } = require('child_process'); execFile('ls', ['-al'], (error, salida estándar, stderr) => { console.log(salida estándar); });
La función de este método es similar a exec
. La única diferencia es que execFile
procesa directamente el comando con el archivo ejecutable especificado (es decir, el valor del file
de parámetros) de forma predeterminada, lo que hace que su eficiencia sea ligeramente mayor que la exec
(Si nos fijamos en la lógica de procesamiento del shell, creo que la eficiencia es insignificante).
Los parámetros de este método se explican a continuación:
file
: el nombre o la ruta del archivo ejecutable;
args
: la lista de parámetros del archivo ejecutable
options
: configuración de parámetros (no se puede especificar), las propiedades relevantes son las siguientes:
shell
: cuando el valor es false
significa usar directamente el archivo ejecutable especificado (es decir, el valor del file
de parámetros) cuando el valor es true
u otras cadenas, la función es equivalente shell
en exec
. El valor predeterminado es false
;windowsVerbatimArguments
: si se deben citar o escapar los parámetros en Windows
. Este atributo se ignorará en Unix
, y el false
timeout
maxBuffer
encoding
cwd
env
killSignal
uid
gid
, windowsHide
y signal
se presentaron anteriormente y no se repetirán aquí.callback
: función de devolución de llamada, que es equivalente a callback
en exec
y no se explicará aquí.
const {fork} = require('child_process'); const eco = fork('./echo.js', { silencio: cierto }); echo.stdout.on('datos', (datos) => { console.log(`stdout: ${datos}`); }); echo.stderr.on('datos', (datos) => { console.error(`stderr: ${datos}`); }); echo.on('cerrar', (código) => { console.log(`el proceso hijo salió con el código ${code}`); });
este método se utiliza para crear una nueva instancia de Node.js para ejecutar el script de Node.js especificado y comunicarse con el proceso principal a través de IPC.
Los parámetros de este método se explican a continuación:
modulePath
: la ruta del script Node.js que se ejecutará;
args
: la lista de parámetros pasada al script Node.js
options
: configuración de parámetros (no se puede especificar), atributos relacionados; tales como:
detached
: consulte a continuación la descripción de spwan
de options.detached
;
execPath
: crea el archivo ejecutable del proceso secundario;
execArgv
: la lista de parámetros de cadena pasada al archivo ejecutable, el valor predeterminado es el valor process.execArgv
serialization
: El tipo de número de serie del mensaje entre procesos, los valores disponibles son json
y advanced
, el valor predeterminado es json
:
slient
es true
, stdin
, stdout
y stderr
del proceso secundario se pasarán al proceso principal; a través de tuberías; de lo contrario, se heredarán stdin
, stdout
y stderr
del proceso principal; el valor predeterminado es false
:
stdio
la descripción de options.stdio
en spwan
a continuación; Lo que hay que tener en cuenta aquí es que
slient
;ipc
(como [0, 1, 2, 'ipc']
); Se lanzará una excepción.Las propiedades cwd
, env
, uid
, gid
, windowsVerbatimArguments
, signal
, timeout
y killSignal
se introdujeron anteriormente y no se repetirán aquí.
const { spawn } = require('child_process'); const ls = spawn('ls', ['-al']); ls.stdout.on('datos', (datos) => { console.log(`stdout: ${datos}`); }); ls.stderr.on('datos', (datos) => { console.error(`stderr: ${datos}`); }); ls.on('cerrar', (código) => { console.log(`el proceso hijo salió con el código ${code}`); });
este método es el método básico del módulo child_process
. exec
, execFile
y fork
eventualmente llamarán spawn
para crear un proceso hijo.
Los parámetros de este método se explican a continuación:
command
: el nombre o la ruta del archivo ejecutable;
args
: la lista de parámetros pasada al archivo ejecutable;
options
: configuración de parámetros (no se puede especificar), los atributos relevantes son los siguientes:
argv0
: enviado al proceso hijo valor argv[0], el valor predeterminado es el valor del parámetro command
detached
: si se permite que el proceso hijo se ejecute independientemente del proceso padre (es decir, después de que el proceso padre sale, el proceso hijo se ejecuta de forma independiente). El proceso puede continuar ejecutándose), el valor predeterminado es false
y cuando su valor es true
, cada plataforma El efecto es el siguiente:
Windows
, después de que sale el proceso principal, el proceso secundario puede continuar ejecutándose y el proceso secundario tiene su propia ventana de consola (una vez que se inicia esta función, no se puede cambiar durante el proceso en ejecuciónWindows
, el proceso secundario actuará como líder del nuevo grupo de sesiones de proceso en este momento); Independientemente de si el proceso hijo está separado del proceso padre, el proceso hijo puede continuar ejecutándose después de que el proceso padre salga.Cabe señalar que si el proceso hijo necesita realizar una tarea a largo plazo y desea que el proceso padre salga antes, se deben cumplir los siguientes puntos al mismo tiempo:
unref
del proceso hijo para eliminar al hijo proceso del bucle de eventos del proceso principal;ignore
stdio
detached
en true
;Por ejemplo, el siguiente ejemplo:
// hola.js const fs = requerir('fs'); dejar índice = 0; ejecución de función() { setTimeout(() => { fs.writeFileSync('./hola', `índice: ${índice}`); si (índice < 10) { índice += 1; correr(); } }, 1000); } correr(); // principal.js const {generar} = requerir('child_process'); const niño = spawn('nodo', ['./hola.js'], { separado: cierto, stdio: 'ignorar' }); child.unref();
stdio
: configuración de entrada y salida estándar del proceso secundario, el valor predeterminado es pipe
, el valor es una cadena o matriz:
pipe
se convierte en ['pipe', 'pipe', 'pipe']
), los valores disponibles son pipe
, overlapped
, ignore
, inherit
stdin
, stdout
y stderr
respectivamente, cada uno de los valores disponibles del elemento es pipe
, overlapped
, ignore
, inherit
, ipc
, objeto Stream, entero positivo (el descriptor de archivo abierto en el proceso principal), null
(si es ubicado en los primeros tres elementos de la matriz, equivale a pipe
, de lo contrario equivale a ignore
), undefined
(si está ubicado en los primeros tres elementos de la matriz, equivale a pipe
, de lo contrario equivale a ignore
).Los atributos cwd
, env
, uid
, gid
, serialization
, shell
(el valor es boolean
o string
), windowsVerbatimArguments
, windowsHide
, signal
, timeout
, killSignal
se han introducido anteriormente y no se repetirán aquí.
Lo anterior ofrece una breve introducción al uso de los métodos principales en el módulo child_process
Dado que execSync
, execFileSync
, forkSync
y spwanSync
son versiones síncronas de exec
, execFile
y spwan
, no hay diferencia en sus parámetros, por lo que. no se repetirán.
A través del módulo cluster
, podemos crear un cluster de procesos Node.js. Al agregar el proceso Node.js al cluster, podemos aprovechar al máximo las ventajas de múltiples núcleos y distribuir tareas del programa a diferentes procesos para mejorar la ejecución. eficiencia del programa; a continuación, usaremos Este ejemplo presenta el uso del módulo cluster
:
const http = require('http'); const clúster = requerir('clúster'); const numCPUs = require('os').cpus().length; si (cluster.isPrimary) { for (sea i = 0; i < numCPU; i++) { cluster.fork(); } } demás { http.createServer((solicitud, res) => { res.writeHead(200); res.end(`${proceso.pid}n`); }).escuchar(8000); }
El ejemplo anterior se divide en dos partes según el juicio del atributo cluster.isPrimary
(es decir, juzgar si el proceso actual es el proceso principal):
cluster.fork
8000
).Ejecute el ejemplo anterior y acceda a http://localhost:8000/
en el navegador. Encontraremos que pid
devuelto es diferente para cada acceso, lo que muestra que la solicitud se distribuye a cada proceso secundario. La estrategia de equilibrio de carga predeterminada adoptada por Node.js es la programación por turnos, que se puede modificar mediante la variable de entorno NODE_CLUSTER_SCHED_POLICY
o cluster.schedulingPolicy
:
NODE_CLUSTER_SCHED_POLICY = rr // o none cluster.schedulingPolicy = cluster.SCHED_RR; // o cluster.SCHED_NONE
Otra cosa a tener en cuenta es que aunque cada proceso hijo ha creado un servidor HTTP y ha escuchado el mismo puerto, eso no significa que estos procesos hijos sean libres de competir. solicitudes de usuario. , porque esto no puede garantizar que la carga de todos los procesos secundarios esté equilibrada. Por lo tanto, el proceso correcto debe ser que el proceso principal escuche el puerto y luego reenvíe la solicitud del usuario a un subproceso específico para su procesamiento de acuerdo con la política de distribución.
Dado que los procesos están aislados entre sí, los procesos generalmente se comunican a través de mecanismos como la memoria compartida, el paso de mensajes y las canalizaciones. Node.js completa la comunicación entre los procesos padre e hijo mediante消息传递
, como en el siguiente ejemplo:
const http = require('http'); const clúster = requerir('clúster'); const numCPUs = require('os').cpus().length; si (cluster.isPrimary) { for (sea i = 0; i < numCPU; i++) { trabajador constante = cluster.fork(); trabajador.on('mensaje', (mensaje) => { console.log(`Soy primario(${process.pid}), recibí un mensaje del trabajador: "${message}"`); trabajador.send(`Enviar mensaje al trabajador`) }); } } demás { proceso.on('mensaje', (mensaje) => { console.log(`Soy trabajador(${process.pid}), recibí un mensaje del primario: "${message}"`) }); http.createServer((solicitud, res) => { res.writeHead(200); res.end(`${proceso.pid}n`); Process.send('Enviar mensaje al principal'); }).escuchar(8000); }
Ejecute el ejemplo anterior y visite http://localhost:8000/
, luego verifique la terminal, veremos un resultado similar al siguiente:
Soy principal (44460), recibí un mensaje del trabajador: "Enviar mensaje al principal" Soy trabajador (44461), recibí un mensaje del primario: "Enviar mensaje al trabajador" Soy principal (44460), recibí un mensaje del trabajador: "Enviar mensaje al principal" Soy trabajador (44462), recibí un mensaje del primario: "Enviar mensaje al trabajador".
Usando este mecanismo, podemos monitorear el estado de cada proceso hijo para que cuando ocurra un accidente en un proceso hijo, podamos intervenir en él a tiempo. para garantizar la Disponibilidad de los Servicios.
La interfaz del módulo cluster
es muy simple. Para ahorrar espacio, aquí solo hacemos algunas declaraciones especiales sobre el método cluster.setupPrimary
. Para otros métodos, consulte la documentación oficial:
cluster.setupPrimary
, la configuración relevante. se sincronizará con el atributo cluster.settings
, y cada llamada se basa en el valor del atributo cluster.settings
actualcluster.setupPrimary
, no tiene ningún impacto en el proceso secundario en ejecución, solo las llamadas posteriores cluster.fork
se ven afectados;cluster.setupPrimary
, no afecta los pases posteriores a cluster.fork
El parámetro env
de la llamadacluster.setupPrimary
solo se puede usar en el proceso principal;Presentamos cluster
anteriormente, a través del cual podemos crear un clúster de procesos Node.js para mejorar la eficiencia de ejecución del programa. Sin embargo, cluster
se basa en el modelo multiproceso, con un alto costo de conmutación entre procesos y aislamiento. de recursos entre procesos. El aumento en el número de procesos secundarios puede conducir fácilmente al problema de no poder responder debido a limitaciones de recursos del sistema. Para resolver tales problemas, Node.js proporciona worker_threads
. A continuación presentamos brevemente el uso de este módulo a través de ejemplos específicos:
// server.js const http = requerir('http'); const { Trabajador } = require('worker_threads'); http.createServer((solicitud, res) => { const httpWorker = nuevo trabajador('./http_worker.js'); httpWorker.on('mensaje', (resultado) => { res.writeHead(200); res.end(`${resultado}n`); }); httpWorker.postMessage('Tom'); }).escuchar(8000); // http_worker.js const {parentPort} = require('worker_threads'); parentPort.on('mensaje', (nombre) => { parentPort.postMessage(`¡Bienvenido ${nombre}!`); });
El ejemplo anterior muestra el uso simple de worker_threads
. Al usar worker_threads
, debe prestar atención a los siguientes puntos:
Cree una instancia de Worker a través de worker_threads.Worker
, donde el script de Worker puede ser un archivo JavaScript
independiente o字符串
, por ejemplo, el ejemplo anterior se puede modificar como:
const code = "const { parentPort } = require('worker_threads'); parentPort.on('message', (name) => {parentPort.postMessage(`Welcone ${ nombre}!` );})"; const httpWorker = new Worker(code, { eval: true });
Al crear una instancia de Worker a través de worker_threads.Worker
, puede configurar los metadatos iniciales del subproceso de Worker especificando el valor de workerData
, como por ejemplo:
// servidor .js const { Trabajador } = require('worker_threads'); const httpWorker = new Worker('./http_worker.js', {workerData: {nombre: 'Tom'} }); // http_worker.js const {workerData} = require('worker_threads'); console.log(workerData)
Al crear una instancia de Worker a través de worker_threads.Worker
, puede configurar SHARE_ENV
para darse cuenta de la necesidad de compartir variables de entorno entre el subproceso de Worker y el hilo principal, por ejemplo:
const {Worker, SHARE_ENV} = require('trabajador_threads '); trabajador const = nuevo trabajador('process.env.SET_IN_WORKER = "foo"', { eval: true, env: SHARE_ENV }); trabajador.on('salir', () => { console.log(proceso.env.SET_IN_WORKER); });
A diferencia del mecanismo de comunicación entre procesos en cluster
, worker_threads
usa MessageChannel para comunicarse entre subprocesos:
parentPort.postMessage
y procesa los mensajes del subproceso principal escuchando message
evento de message
del mensaje parentPort
;httpWorker
a través del método postMessage
de la instancia del subproceso Worker (aquí está httpWorker
, y se reemplaza por este subproceso Worker a continuación) y procesa mensajes del subproceso Worker; escuchando el evento message
de httpWorker
.En Node.js, ya sea un proceso hijo creado por cluster
o un hilo hijo de trabajador creado por worker_threads
, todos tienen su propia instancia V8 y su propio bucle de eventos. La diferencia es que
Aunque parece que los subprocesos de Worker son más eficientes que los procesos secundarios, los subprocesos de Worker también tienen desventajas, es decir, cluster
proporciona equilibrio de carga, mientras que worker_threads
requiere que nosotros mismos completemos el diseño y la implementación del equilibrio de carga.
Este artículo presenta el uso de los tres módulos child_process
, cluster
y worker_threads
en Node.js. A través de estos tres módulos, podemos aprovechar al máximo las ventajas de las CPU de múltiples núcleos y resolver de manera eficiente algunos problemas especiales en un subproceso múltiple (. hilo) modo La eficiencia operativa de las tareas (como IA, procesamiento de imágenes, etc.). Cada módulo tiene sus escenarios aplicables. Este artículo solo explica su uso básico. Aún debe explorar usted mismo cómo usarlo de manera eficiente en función de sus propios problemas. Finalmente, si hay algún error en este artículo, espero que puedan corregirlo. Les deseo a todos una feliz codificación todos los días.