¿Cuándo se usa el grupo de subprocesos?

104

Entonces, entiendo cómo funciona Node.js: tiene un solo hilo de escucha que recibe un evento y luego lo delega a un grupo de trabajadores. El hilo de trabajo notifica al oyente una vez que completa el trabajo, y el oyente luego devuelve la respuesta al llamador.

Mi pregunta es la siguiente: si levanto un servidor HTTP en Node.js y llamo a sleep en uno de los eventos de mi ruta enrutada (como "/ test / sleep"), todo el sistema se detiene. Incluso el hilo de un solo oyente. Pero tengo entendido que este código está sucediendo en el grupo de trabajadores.

Ahora, por el contrario, cuando uso Mongoose para hablar con MongoDB, las lecturas de DB son una operación de E / S costosa. El nodo parece poder delegar el trabajo a un hilo y recibir la devolución de llamada cuando se completa; el tiempo necesario para cargar desde la base de datos no parece bloquear el sistema.

¿Cómo decide Node.js utilizar un hilo de grupo de subprocesos frente al hilo de escucha? ¿Por qué no puedo escribir un código de evento que duerma y solo bloquee un subproceso del grupo de subprocesos?

Haney
fuente
@Tobi - Lo he visto. Todavía no responde a mi pregunta. Si el trabajo estaba en otro hilo, el sueño solo afectaría a ese hilo y no también al oyente.
Haney
8
Una pregunta genuina, en la que intentas entender algo por ti mismo, y cuando no puedes encontrar una salida al laberinto, pides ayuda.
Rafael Eyng

Respuestas:

240

Su comprensión de cómo funciona el nodo no es correcta ... pero es un error común, porque la realidad de la situación es bastante compleja y, por lo general, se reduce a pequeñas frases concisas como "el nodo es de un solo subproceso" que simplifican demasiado las cosas .

Por el momento, ignoraremos el multiproceso / multiproceso explícito a través del clúster y los subprocesos de trabajo web , y solo hablaremos del nodo típico sin subprocesos.

El nodo se ejecuta en un solo bucle de eventos. Es de un solo hilo, y solo obtienes ese hilo. Todo el javascript que escribe se ejecuta en este bucle, y si ocurre una operación de bloqueo en ese código, bloqueará todo el bucle y no sucederá nada más hasta que finalice. Esta es la naturaleza típicamente de un solo subproceso del nodo de la que tanto se habla. Pero no es el panorama completo.

Ciertas funciones y módulos, generalmente 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 fsmódulo para solicitar un archivo, el fsmódulo pasa esa llamada a un hilo de trabajo, y ese trabajador espera su respuesta, que luego presenta al bucle de eventos que se ha estado agitando sin él en el mientras tanto. Todo esto se extrae de usted, el desarrollador de nodos, y parte de esto se extrae de los desarrolladores de módulos mediante el uso de libuv .

Como señaló Denis Dollfus en los comentarios (de esta respuesta a una pregunta similar), la estrategia utilizada por libuv para lograr E / S asíncronas no siempre es un grupo de subprocesos, específicamente en el caso del httpmódulo, una estrategia diferente parece ser utilizado en este momento. Para nuestros propósitos aquí, es principalmente importante notar cómo se logra el contexto asincrónico (usando libuv) y que el grupo de subprocesos mantenido por libuv es una de las múltiples estrategias que ofrece esa biblioteca para lograr la asincronicidad.


En una tangente principalmente relacionada, hay un análisis mucho más profundo de cómo el nodo logra la asincronicidad, y algunos problemas potenciales relacionados y cómo lidiar con ellos, en este excelente artículo . La mayor parte se expande sobre lo que he escrito anteriormente, pero además señala:

  • Es probable que cualquier módulo externo que incluya en su proyecto que haga uso de C ++ nativo y libuv use el grupo de subprocesos (piense: acceso a la base de datos)
  • libuv tiene un tamaño de grupo de subprocesos predeterminado de 4, y usa una cola para administrar el acceso al grupo de subprocesos; el resultado es que si tiene 5 consultas de base 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) estará esperando a que esas consultas terminen antes de que comiencen
  • Puede mitigar esto aumentando el tamaño del grupo de subprocesos a través de la UV_THREADPOOL_SIZEvariable de entorno, siempre que lo haga antes de que se requiera y se cree el grupo de subprocesos:process.env.UV_THREADPOOL_SIZE = 10;

Si desea un multiproceso o subprocesamiento múltiple tradicional en el nodo, puede obtenerlo a través del clustermódulo integrado o varios otros módulos como el mencionado anteriormente webworker-threads, o puede falsificarlo implementando alguna forma de dividir su trabajo y usar manualmente setTimeouto setImmediateo process.nextTickpara pausar su trabajo y continuarlo en un ciclo posterior para permitir que se completen otros procesos (pero eso no es recomendable).

Tenga en cuenta que si está escribiendo código de bloqueo / ejecución prolongada en javascript, probablemente esté cometiendo un error. Otros idiomas funcionarán de manera mucho más eficiente.

Jason
fuente
1
Santa mierda, esto me aclara por completo. ¡Muchas gracias @Jason!
Haney
5
No hay problema :) Me encontré donde estás no hace mucho tiempo, y fue difícil llegar a una respuesta bien definida porque por un lado tienes desarrolladores de C / C ++ para quienes la respuesta es obvia, y por el otro tienes los típicos desarrolladores web que no han profundizado demasiado en este tipo de preguntas antes. Ni siquiera estoy seguro de que mi respuesta sea 100% técnicamente correcta cuando llegas al nivel C, pero es correcta a grandes rasgos.
Jason
3
Usar el grupo de subprocesos para solicitudes de red sería un gran desperdicio de recursos. Según esta pregunta "Hace la E / S de red asíncrona basada en las interfaces de E / S asíncrona en diferentes plataformas, como epoll, kqueue e IOCP, sin un grupo de subprocesos" - lo cual tiene sentido.
Denis Dollfus
1
... dicho esto, si realiza un trabajo pesado en el hilo principal de javascript directamente, o no tiene suficientes recursos o no los administra adecuadamente para dar suficiente margen para el grupo de hilos, podría introducir un retraso en una concurrencia más baja umbral: el resultado es que, para los mismos recursos del sistema, normalmente experimentará un rendimiento más alto con node.js que con otras opciones (aunque hay otros sistemas basados ​​en eventos en otros lenguajes que tienen como objetivo desafiar eso, no he sin embargo, visto los puntos de referencia recientes): está claro que un modelo basado en eventos supera a un modelo con subprocesos.
Jason
1
@Aabid El hilo de escucha no ejecuta una consulta de base de datos, por lo que tomará aproximadamente 6 segundos completar las 10 consultas (por el tamaño predeterminado del grupo de hilos de 4). Si necesita hacer algún trabajo en javascript que no requiera que se completen los resultados de esa consulta de la base de datos, por ejemplo, ingresan más solicitudes que no requieren que el grupo de subprocesos complete ningún trabajo asincrónico, continuará funcionando en el principal bucle de eventos.
Jason
20

Entonces, entiendo cómo funciona Node.js: tiene un solo hilo de escucha que recibe un evento y luego lo delega a un grupo de trabajadores. El hilo de trabajo notifica al oyente una vez que completa el trabajo, y el oyente luego devuelve la respuesta al llamador.

Esto no es realmente exacto. Node.js tiene un solo hilo "trabajador" que ejecuta javascript. Hay subprocesos dentro del nodo que manejan el procesamiento de E / S, pero pensar en ellos como "trabajadores" es un error. En realidad, solo hay manejo de IO y algunos otros detalles de la implementación interna del nodo, pero como programador no puede influir en su comportamiento más que unos pocos parámetros misceláneos como MAX_LISTENERS.

Mi pregunta es la siguiente: si levanto un servidor HTTP en Node.js y llamo a sleep en uno de los eventos de mi ruta enrutada (como "/ test / sleep"), todo el sistema se detiene. Incluso el hilo de un solo oyente. Pero tengo entendido que este código está sucediendo en el grupo de trabajadores.

No hay ningún mecanismo de suspensión en JavaScript. Podríamos discutir esto de manera más concreta si publicaras un fragmento de código de lo que crees que significa "dormir". No existe tal función para llamar para simular algo como time.sleep(30)en Python, por ejemplo. Hay setTimeoutpero que es fundamentalmente no duerme. setTimeouty liberarsetInterval explícitamente , no bloquear, 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 hacer un bucle ocupado en la CPU con el cálculo en memoria, lo que de hecho hará que el subproceso de ejecución principal deje de responder y su programa no responda.

¿Cómo decide Node.js utilizar un hilo de grupo de subprocesos frente al hilo de escucha? ¿Por qué no puedo escribir un código de evento que duerma y solo bloquee un subproceso del grupo de subprocesos?

La E / S de red siempre es asincrónica. Fin de la historia. Disk IO tiene API síncronas y asincrónicas, por lo que no hay "decisión". node.js se comportará de acuerdo con las funciones principales 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 separada child_process.execy child_process.execSyncAPIs.

La regla general es usar siempre las API asincrónicas. Las razones válidas para usar las API de sincronización son el código de inicialización en un servicio de red antes de que esté escuchando conexiones o en scripts simples que no aceptan solicitudes de red para herramientas de compilación y ese tipo de cosas.

Peter Lyons
fuente
1
¿De dónde provienen estas API asincrónicas? Entiendo lo que está diciendo, pero quien haya escrito estas API optó por IOCP / async. ¿Cómo eligieron hacer esto?
Haney
3
Su pregunta es cómo escribiría su propio código intensivo en tiempo y no bloquearía.
Jason
1
Si. Node proporciona redes básicas UDP, TCP y HTTP. Proporciona ÚNICAMENTE API asincrónicas "basadas en grupos". Todo el código de node.js del mundo sin excepción utiliza estas API asincrónicas basadas en grupos, ya que simplemente hay todo lo que está disponible. El sistema de archivos y los procesos secundarios son una historia diferente, pero las redes son consistentemente asincrónicas.
Peter Lyons
4
Cuidado, Peter, no sea que seas el proverbial cazo de su tetera. Quiere saber cómo lo hicieron los escritores de la API de red, no cómo lo hacen las personas que usan la API de red. Finalmente, comprendí cómo se comporta el nodo en relación con los eventos sin bloqueo porque quería escribir mi propio código sin bloqueo que no tiene nada que ver con las redes ni con ninguna de las otras API asincrónicas integradas. Está bastante claro que David quiere hacer lo mismo.
Jason
2
El nodo no usa grupos de subprocesos para IO, usa IO nativo sin bloqueo, la única excepción es fs, hasta donde yo sé
vkurchatkin
2

Grupo de hilos cómo cuándo y quién lo utilizó:

En primer lugar, cuando usamos / instalamos Node en una computadora, inicia un proceso entre otros procesos que se llama proceso de nodo en la computadora, y sigue ejecutándose hasta que lo matas. Y este proceso en ejecución es nuestro llamado hilo único.

ingrese la descripción de la imagen aquí

Por lo tanto, el mecanismo de un solo hilo facilita el bloqueo de una aplicación de nodo, pero esta es una de las características únicas que Node.js trae a la mesa. Entonces, nuevamente, si ejecuta su aplicación de nodo, se ejecutará en un solo hilo. No importa si tiene 1 o un millón de usuarios accediendo a su aplicación al mismo tiempo.

Entonces, entendamos exactamente qué sucede en el hilo único de nodejs cuando inicia su aplicación de nodo. Al principio, se inicializa el programa, luego se ejecuta todo el código de nivel superior, lo que significa que todos los códigos que no están dentro de ninguna función de devolución de llamada ( recuerde que todos los códigos dentro de todas las funciones de devolución de llamada se ejecutarán en el bucle de eventos ).

Después de eso, todo el código de los módulos ejecutado y luego registra toda la devolución de llamada, finalmente, se inició el ciclo de eventos para su aplicación.

ingrese la descripción de la imagen aquí

Entonces, como discutimos antes, todas las funciones de devolución de llamada y los códigos dentro de esas funciones se ejecutarán en el bucle de eventos. En el bucle de eventos, las cargas se distribuyen en diferentes fases. De todos modos, no voy a discutir sobre el ciclo de eventos aquí.

Bueno, en aras de una mejor comprensión del grupo de subprocesos, le solicito que imagine que en el bucle de eventos, los códigos dentro de una función de devolución de llamada se ejecutan después de completar la ejecución de códigos dentro de otra función de devolución de llamada, ahora si hay algunas tareas son realmente demasiado pesadas. Luego bloquearían nuestro hilo único de nodejs. Y ahí es donde entra el grupo de subprocesos, que es como el bucle de eventos, que la biblioteca libuv proporciona a Node.js.

Por lo tanto, el grupo de subprocesos no es parte de nodejs en sí mismo, libuv lo proporciona para descargar tareas pesadas a libuv, y libuv ejecutará esos códigos en sus propios subprocesos y, después de la ejecución, libuv devolverá los resultados al evento en el bucle de eventos.

ingrese la descripción de la imagen aquí

El grupo de subprocesos nos da cuatro subprocesos adicionales, que están completamente separados del subproceso principal único. Y de hecho podemos configurarlo hasta 128 hilos.

Entonces, todos estos hilos juntos formaron un grupo de hilos. y el bucle de eventos puede descargar automáticamente tareas pesadas al grupo de subprocesos.

La parte divertida es que todo esto sucede automáticamente detrás de escena. No somos nosotros los desarrolladores quienes decidimos qué va al grupo de subprocesos y qué no.

Hay muchas tareas que van al grupo de subprocesos, como

-> All operations dealing with files
->Everyting is related to cryptography, like caching passwords.
->All compression stuff
->DNS lookups
Señor
fuente
0

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.

Gregory R. Sudderth
fuente