sistema - node.js server-side javascript
¿Cuándo se usa el grupo de subprocesos? (3)
Así que entiendo cómo funciona Node.js: tiene un único hilo de escucha que recibe un evento y luego lo delega en un grupo de trabajadores. El subproceso de trabajador notifica al oyente una vez que completa el trabajo, y el oyente luego devuelve la respuesta a la persona que llama.
Esto no es realmente exacto. Node.js tiene solo un hilo de "trabajador" que realiza la ejecución de javascript. Hay hilos dentro del nodo que manejan el procesamiento de IO, pero pensar en ellos como "trabajadores" es una idea errónea. En realidad, solo se trata de IO y algunos otros detalles de la implementación interna del nodo, pero como programador no se puede influir en su comportamiento, salvo algunos parámetros diversos, como MAX_LISTENERS.
Mi pregunta es esta: si pongo de pie un servidor HTTP en Node.js y llamo a suspensión en uno de mis eventos de ruta enrutada (como "/ test / sleep"), todo el sistema se detiene. Incluso el hilo de oyente único. Pero entendí que este código está sucediendo en el grupo de trabajadores.
No hay mecanismo de suspensión en JavaScript. Podríamos discutir esto más concretamente si publicaste un fragmento de código de lo que crees que significa "dormir". No hay tal función para llamar para simular algo como time.sleep(30)
en python, por ejemplo. Hay setTimeout
pero eso es fundamentalmente NO dormir. setTimeout
y setInterval
liberan explícitamente, no bloquean, el bucle de eventos para que otros bits de código puedan ejecutarse en el hilo de ejecución principal. Lo único que puede hacer es ocupar el bucle de la CPU con el cómputo en memoria, lo que de hecho privará al hilo de ejecución principal y hará que su programa no responda.
¿Cómo decide Node.js utilizar un subproceso de grupo de subprocesos frente al subproceso de escucha? ¿Por qué no puedo escribir el código de evento que duerme y solo bloquea un hilo del grupo de subprocesos?
La red IO siempre es asincrónica. Fin de la historia. Disk IO tiene API síncronas y asíncronas, por lo que no hay "decisión". node.js se comportará de acuerdo con las funciones básicas de la API a las que llama sync vs async normal. Por ejemplo: fs.readFile
vs fs.readFileSync
. Para los procesos secundarios, también hay diferentes child_process.exec
y child_process.execSync
APIs.
La regla general siempre es usar las API asincrónicas. Las razones válidas para usar las API de sincronización son para el código de inicialización en un servicio de red antes de escuchar conexiones o en scripts simples que no aceptan solicitudes de red para herramientas de compilación y ese tipo de cosas.
Así que entiendo cómo funciona Node.js: tiene un único hilo de escucha que recibe un evento y luego lo delega en un grupo de trabajadores. El subproceso de trabajador notifica al oyente una vez que completa el trabajo, y el oyente luego devuelve la respuesta a la persona que llama.
Mi pregunta es esta: si pongo de pie un servidor HTTP en Node.js y llamo a suspensión en uno de mis eventos de ruta enrutada (como "/ test / sleep"), todo el sistema se detiene. Incluso el hilo de oyente único. Pero entendí que este código está sucediendo en el grupo de trabajadores.
Ahora, en cambio, cuando uso Mongoose para hablar con MongoDB, las lecturas de DB son una costosa operación de E / S. Nodo parece ser capaz de delegar el trabajo a un hilo y recibir la devolución de llamada cuando se completa; el tiempo que lleva cargar el DB no parece bloquear el sistema.
¿Cómo decide Node.js utilizar un subproceso de grupo de subprocesos frente al subproceso de escucha? ¿Por qué no puedo escribir el código de evento que duerme y solo bloquea un hilo del grupo de subprocesos?
Este malentendido es simplemente la diferencia entre la multitarea preventiva y la multitarea cooperativa ...
El sueño apaga todo el carnaval porque en realidad hay una línea para todos los juegos, y cerraste la puerta. Piense en ello como "un intérprete de JS y algunas otras cosas" e ignore los hilos ... para usted, solo hay un hilo, ...
... así que no lo bloquees.
Su comprensión de cómo funciona el nodo no es correcta ... pero es un concepto erróneo común, porque la realidad de la situación es realmente bastante compleja, y por lo general se reduce a pequeñas frases concisas como "nodo tiene un único hilo" que simplifican demasiado las cosas .
Por el momento, ignoraremos el multi-procesamiento / multi-threading explícito a través de cluster y webworker-threads , y solo hablaremos del típico nodo no-subprocesado.
El nodo se ejecuta en un único ciclo de eventos. Es de un solo hilo, y solo tienes ese hilo. Todos los javascript que escribe se ejecutan en este ciclo, y si ocurre una operación de bloqueo en ese código, bloqueará todo el ciclo y no pasará nada hasta que finalice. Esta es la naturaleza típica de un solo hilo del que escuchas tanto. Pero, no es la imagen completa.
Ciertas funciones y módulos, normalmente escritos en C / C ++, admiten E / S asíncronas. Cuando llamas a estas funciones y métodos, administran internamente el paso de la llamada a un hilo de trabajo. Por ejemplo, cuando usa el módulo fs
para solicitar un archivo, el módulo fs
pasa esa llamada a un hilo de trabajo, y ese trabajador espera su respuesta, que luego presenta de nuevo al bucle de evento que ha estado funcionando sin él. mientras tanto. Todo esto se abstrae de usted, el desarrollador de nodo, y parte de él se abstrae de los desarrolladores de módulos mediante el uso de libuv .
Como señala Denis Dollfus en los comentarios (de esta respuesta a una pregunta similar), la estrategia utilizada por libuv para lograr E / S asíncrona no siempre es un grupo de subprocesos, específicamente en el caso del módulo http
una estrategia diferente parece ser usado en este momento. Para nuestros propósitos aquí, es importante destacar cómo se logra el contexto asincrónico (mediante el uso de libuv) y que el grupo de subprocesos mantenido por libuv es una de las múltiples estrategias que ofrece esa biblioteca para lograr la asincronía.
En una tangente relacionada en su mayoría, hay un análisis mucho más profundo de cómo el nodo logra la asincronía, y algunos problemas potenciales relacionados y cómo lidiar con ellos, en este excelente artículo . La mayor parte se expande en lo que he escrito arriba, pero adicionalmente señala:
- Es probable que cualquier módulo externo que incluya en su proyecto que utiliza C ++ nativo y libuv use el grupo de subprocesos (pensemos: acceso a la base de datos)
- libuv tiene un tamaño de grupo de subprocesos predeterminado de 4 y utiliza una cola para administrar el acceso al grupo de subprocesos; el resultado es que si tiene 5 consultas de bases de datos de larga ejecución todas al mismo tiempo, una de ellas (y cualquier otra asincrónica) acción que se basa en el grupo de subprocesos) esperará a que esas consultas finalicen antes de que comiencen
- Puede mitigar esto aumentando el tamaño del grupo de subprocesos a través de la variable de entorno
UV_THREADPOOL_SIZE
, siempre que lo haga antes de que se requiera y cree el grupo de subprocesos:process.env.UV_THREADPOOL_SIZE = 10;
Si desea multiprocesamiento tradicional o multi-threading en un nodo, puede obtenerlo a través del módulo de cluster
incorporado o de otros módulos, como los webworker-threads
antes mencionados, o puede webworker-threads
implementando alguna forma de fragmentar su trabajo. y usar manualmente setTimeout
o setImmediate
o process.nextTick
para pausar su trabajo y continuarlo en un bucle posterior para permitir que se completen otros procesos (pero eso no se recomienda).
Tenga en cuenta que si escribe código de larga ejecución / bloqueo en JavaScript, probablemente esté cometiendo un error. Otros idiomas funcionarán mucho más eficientemente.