Pregunta ¿Cuándo se usa el grupo de subprocesos?


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?


75
2018-03-25 19:20


origen


Respuestas:


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 explícito / multi-threading a través de racimo y webworker-hilos, y solo habla sobre un nodo típico 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 fs módulo para solicitar un archivo, el fs el módulo pasa esa llamada a un hilo de trabajo, y ese trabajador espera su respuesta, que luego vuelve a presentar al bucle de evento que se ha estado produciendo sin él mientras tanto. Todo esto se abstrae de usted, el desarrollador del nodo, y parte de él se abstrae de los desarrolladores del módulo 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 asincrónicas no siempre es un grupo de subprocesos, específicamente en el caso de http módulo, una estrategia diferente parece ser utilizada 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 en su mayoría relacionada, hay un análisis mucho más profundo de cómo los nodos logran 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 del UV_THREADPOOL_SIZE variable de entorno, siempre que lo haga antes de que se requiera y cree el grupo de subprocesos: process.env.UV_THREADPOOL_SIZE = 10;

Si desea un multiproceso tradicional o multi-threading en un nodo, puede obtenerlo a través del cluster módulo o varios otros módulos como el mencionado webworker-threads, o puede fingir implementando alguna forma de fragmentar su trabajo y usarlo manualmente setTimeout o setImmediate o process.nextTick para pausar su trabajo y continuarlo en un ciclo 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.


171
2018-03-25 19:44



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 así como time.sleep(30) en python, por ejemplo. Hay setTimeout pero eso es fundamentalmente NO dormir. setTimeout y setInterval explícitamente lanzamiento, 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 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 procesos secundarios, también hay otros 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.


14
2018-03-25 19:38



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.


0
2018-04-02 22:56