works nodejs node loop how event cleartimeout javascript node.js event-loop libev

javascript - how - Nodejs Event Loop



setimmediate node (5)

¿Existen internamente dos bucles de eventos en la arquitectura nodejs?

  • libev / libuv
  • v8 javascript event loop

En una solicitud de E / S, el nodo pone en cola la solicitud a libeio, que a su vez notifica la disponibilidad de datos a través de eventos utilizando libev y finalmente esos eventos son manejados por el bucle de eventos v8 mediante devoluciones de llamadas.

Básicamente, ¿cómo se integran libev y libeio en la arquitectura de nodejs?

¿Hay alguna documentación disponible para dar una imagen clara de la arquitectura interna del nodo?


Hay un ciclo de eventos en la arquitectura de NodeJs.

Node.js Event Loop Model

Las aplicaciones de nodo se ejecutan en un modelo basado en eventos de un solo subproceso. Sin embargo, Node implementa un grupo de subprocesos en el fondo para que se pueda realizar el trabajo.

Node.js agrega trabajo a una cola de eventos y luego tiene un único hilo que ejecuta un ciclo de eventos para recogerlo. El bucle de evento toma el elemento superior en la cola de eventos, lo ejecuta y luego toma el siguiente elemento.

Cuando se ejecuta código que tiene una vida más larga o tiene E / S de bloqueo, en lugar de llamar directamente a la función, agrega la función a la cola de eventos junto con una devolución de llamada que se ejecutará después de que la función finalice. Cuando todos los eventos en la cola de eventos Node.js se han ejecutado, la aplicación Node.js finaliza.

El ciclo de eventos comienza a tener problemas cuando las funciones de nuestra aplicación bloquean las E / S.

Node.js utiliza devoluciones de llamada de eventos para evitar tener que esperar para bloquear E / S. Por lo tanto, cualquier solicitud que realice bloqueo de E / S se realiza en un subproceso diferente en el fondo.

Cuando un evento que bloquea E / S se recupera de la cola de eventos, Node.js recupera un hilo del grupo de subprocesos y ejecuta la función allí en lugar de en el subproceso de bucle de evento principal. Esto evita que la E / S de bloqueo retenga el resto de los eventos en la cola de eventos.


He estado leyendo personalmente el código fuente de node.js y v8.

Entré en un problema similar al tuyo cuando traté de comprender la arquitectura node.js para escribir módulos nativos.

Lo que estoy publicando aquí es mi comprensión de node.js y esto también puede ser un poco fuera de lugar.

  1. Libev es el bucle de eventos que realmente se ejecuta internamente en node.js para realizar operaciones simples de bucle de eventos. Está escrito originalmente para sistemas * nix. Libev proporciona un bucle de evento simple pero optimizado para que el proceso se ejecute. Puedes leer más sobre libev here .

  2. LibEio es una biblioteca para realizar salidas de entrada de forma asincrónica. Maneja descriptores de archivos, manejadores de datos, tomas de corriente, etc. Puedes leer más sobre esto aquí here .

  3. LibUv es una capa de abstracción en la parte superior de libeio, libev, c-are (para DNS) y iocp (para windows asyncronous-io). LibUv realiza, mantiene y administra todos los io y eventos en el grupo de eventos. (en caso de threadpool libeio). Deberías consultar el tutorial de Ryan Dahl sobre libUv. Eso comenzará a tener más sentido para usted sobre cómo funciona libUv y luego comprenderá cómo node.js funciona en la parte superior de libuv y v8.

Para entender solo el ciclo de eventos javascript, debería considerar ver estos videos

Para ver cómo se usa libeio con node.js para crear módulos asíncronos, debería ver este ejemplo .

Básicamente, lo que sucede dentro de node.js es que el bucle v8 se ejecuta y maneja todas las partes de JavaScript, así como los módulos de C ++ [cuando se ejecutan en un hilo principal (según la documentación oficial node.js tiene un solo hilo)]. Cuando están fuera del hilo principal, libev y libeio lo manejan en el grupo de hilos y liberan la interacción con el ciclo principal. Por lo tanto, desde mi comprensión, node.js tiene 1 ciclo de eventos permanentes: ese es el ciclo de eventos v8. Para manejar las tareas asíncronas de C ++, está utilizando un subproceso de subprocesos [a través de libeio & libev].

Por ejemplo:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

Lo que aparece en todos los módulos suele llamar a la función Task en el grupo de hilos. Cuando está completo, llama a la función AfterTask en el hilo principal. Mientras que Eio_REQUEST es el controlador de solicitudes que puede ser una estructura / objeto cuyo motivo es proporcionar comunicación entre el grupo de hilos y el canal principal.


Parece que algunas de las entidades discutidas (p. Ej .: libev, etc.) han perdido relevancia, debido al hecho de que ha pasado un tiempo, pero creo que la pregunta aún tiene un gran potencial.

Permítanme intentar explicar el funcionamiento del modelo impulsado por eventos con la ayuda de un ejemplo abstracto, en un entorno UNIX abstracto, en el contexto de Node, a partir de hoy.

La perspectiva del programa:

  • El motor de scripts inicia la ejecución del script.
  • Cada vez que se encuentra una operación vinculada a la CPU, se ejecuta en línea (máquina real), en su totalidad.
  • Cada vez que se encuentra una operación enlazada de E / S, la solicitud y su controlador de finalización se registran con una ''maquinaria de eventos'' (máquina virtual)
  • Repita las operaciones de la misma manera anterior hasta que finalice el guión. Operación enlazada a la CPU: ejecute en línea, limites de E / S, solicite a la maquinaria como se indica arriba.
  • Cuando se completa la E / S, se vuelve a llamar a los oyentes.

La maquinaria de eventos anterior se llama libuv AKA event loop framework. Nodo aprovecha esta biblioteca para implementar su modelo de programación impulsado por eventos.

La perspectiva del nodo:

  • Tener un hilo para alojar el tiempo de ejecución.
  • Recoge la secuencia de comandos del usuario.
  • Compilarlo en nativo [apalancamiento v8]
  • Carga el binario y salta al punto de entrada.
  • El código compilado ejecuta las actividades vinculadas de la CPU en línea, utilizando primitivas de programación.
  • Muchos I / O y el código relacionado con el temporizador tienen envolturas nativas. Por ejemplo, E / S de red.
  • Por lo tanto, las llamadas de E / S se enrutan desde el script a los puentes C ++, con el manejador de E / S y el manejador de finalización pasados ​​como argumentos.
  • El código nativo ejercita el bucle libuv. Adquiere el ciclo, pone en cola un evento de bajo nivel que representa la E / S y un contenedor de devolución de llamada nativo en la estructura de bucle de libuv.
  • El código nativo vuelve a la secuencia de comandos: ¡no se realiza E / S en este momento!
  • Los elementos anteriores se repiten varias veces, hasta que se ejecute todo el código que no es de E / S y se registre todo el código de E / S de la libuv.
  • Finalmente, cuando no queda nada en el sistema para ejecutar, el nodo pasa el control a libuv
  • libuv entra en acción, recoge todos los eventos registrados, consulta el sistema operativo para obtener su operatividad.
  • Aquellos que están listos para E / S en un modo sin bloqueo, se recogen, se realizan E / S y se emiten sus devoluciones de llamada. Uno después del otro.
  • Aquellos que aún no están listos (por ejemplo, una lectura de socket, para la cual el otro punto final no ha escrito nada) continuarán sondeándose con el sistema operativo hasta que estén disponibles.
  • El ciclo mantiene internamente un temporizador cada vez mayor. Cuando la aplicación solicita una devolución de llamada diferida (como setTimeout), este valor de temporizador interno se aprovecha para calcular el momento adecuado para activar la devolución de llamada.

Si bien la mayoría de las funcionalidades se atienden de esta manera, algunas (versiones asincrónicas) de las operaciones de archivos se llevan a cabo con la ayuda de subprocesos adicionales, bien integrados en libuv. Mientras que las operaciones de E / S de red pueden esperar a la espera de un evento externo, como que el otro extremo responda con datos, etc., las operaciones de archivos necesitan cierto trabajo del nodo en sí. Por ejemplo, si abre un archivo y espera a que el fd esté listo con datos, no sucederá, ya que ¡nadie está leyendo realmente! Al mismo tiempo, si lee el archivo en línea en el hilo principal, puede bloquear potencialmente otras actividades en el programa y puede presentar problemas visibles, ya que las operaciones de archivo son muy lentas en comparación con las actividades vinculadas a la CPU. De modo que los subprocesos de trabajo interno (configurables a través de la variable de entorno UV_THREADPOOL_SIZE) se emplean para operar en archivos, mientras que la abstracción dirigida por eventos funciona intacta, desde la perspectiva del programa.

Espero que esto ayude.


Solo hay un ciclo de eventos proporcionado por libuv, V8 es solo un motor de tiempo de ejecución JS.


Una introducción a libuv

El proyecto node.js comenzó en 2009 como un entorno de JavaScript desacoplado del navegador. Utilizando el V8 de Google y el archivo libev de Marc Lehmann, node.js combinó un modelo de E / S, con un lenguaje que se adecuaba bien al estilo de programación; debido a la forma en que los navegadores lo configuraron A medida que node.js creció en popularidad, era importante hacerlo funcionar en Windows, pero libev solo se ejecutó en Unix. El equivalente de Windows de los mecanismos de notificación de eventos kernel como kqueue o (e) poll es IOCP. libuv era una abstracción en torno a libev o IOCP según la plataforma, proporcionando a los usuarios una API basada en libev. En la versión node-v0.9.0 de libuv libev fue eliminado .

También una imagen que describe el lazo del evento en Node.js por @ BusyRich

Actualización 05/09/2017

Por este ciclo de evento doc Node.js ,

El siguiente diagrama muestra una descripción general simplificada del orden de operaciones del bucle de eventos.

┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘

nota: cada caja se denominará una "fase" del ciclo de eventos.

Descripción de las fases

  • temporizadores : esta fase ejecuta devoluciones de llamada programadas por setTimeout() y setInterval() .
  • Devoluciones de llamada de E / S : ejecuta casi todas las devoluciones de llamada con la excepción de
  • devoluciones de llamada cercanas , las programadas por temporizadores y setImmediate() . inactivo, preparar: solo se usa internamente.
  • encuesta : recuperar nuevos eventos de E / S; nodo se bloqueará aquí cuando sea apropiado.
  • check : se setImmediate() devoluciones de llamada aquí.
  • devoluciones de llamada cercanas : por ejemplo, socket.on(''close'', ...) .

Entre cada ejecución del ciclo de eventos, Node.js comprueba si está esperando una E / S o temporizadores asíncronos y se cierra sin problemas si no hay ninguno.