Nodejs Event Loop


141

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

  • libev / libuv
  • bucle de eventos v8 javascript

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 usando libev y finalmente esos eventos son manejados por el bucle de eventos v8 usando devoluciones de llamada?

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 de nodejs?

Respuestas:


175

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

Entré en un problema similar como tú cuando traté de entender la arquitectura de node.js para escribir módulos nativos.

Lo que estoy publicando aquí es mi comprensión de node.js y esto también podría estar 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 bucles de eventos. Está escrito originalmente para sistemas * nix. Libev proporciona un ciclo de eventos simple pero optimizado para que el proceso se ejecute. Puedes leer más sobre libev aquí .

  2. LibEio es una biblioteca para realizar entradas y salidas de forma asincrónica. Maneja descriptores de archivos, manejadores de datos, sockets, etc. Puede leer más sobre esto aquí .

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

Para entender solo el bucle de eventos de 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 C ++ [cuando se ejecutan en un hilo principal (según la documentación oficial, node.js en sí es de un solo subproceso)]. Cuando está fuera del hilo principal, libev y libeio lo manejan en el grupo de hilos y libev proporciona la interacción con el bucle principal. Entonces, según tengo entendido, node.js tiene 1 bucle de eventos permanente: ese es el bucle de eventos v8. Para manejar las tareas asíncronas de C ++ está utilizando un conjunto de subprocesos [a través de libeio y libev].

Por ejemplo:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

Lo que aparece en todos los módulos suele llamar a la función Tasken el conjunto de subprocesos. Cuando está completo, llama a la AfterTaskfunción en el hilo principal. Mientras Eio_REQUESTque el manejador de solicitudes puede ser una estructura / objeto cuyo motivo es proporcionar comunicación entre el conjunto de subprocesos y el subproceso principal.


Confiar en el hecho de que libuv usa libev internamente es una buena manera de hacer que su código no sea multiplataforma. Solo debe preocuparse por la interfaz pública de libuv.
Raynos

1
@Raynos libuv tiene como objetivo asegurarse de que x-platfousing múltiples bibliotecas. Derecho ? por lo tanto, usar libuv es una buena idea
ShrekOverflow

1
@Abhishek From Doc process.nextTick: en el siguiente bucle alrededor del bucle de eventos, llame a esta devolución de llamada. Este no es un simple alias para setTimeout (fn, 0), es mucho más eficiente. ¿A qué bucle de evento se refiere esto? V8 evento de bucle?
Tamil

2
Tenga en cuenta que libuv ya no se implementa por encima de libev .
strcat

44
¿Hay alguna manera de "ver" este evento que? Me gustaría poder ver el orden de las llamadas en la pila y ver nuevas funciones que se introducen allí para comprender mejor lo que está sucediendo ... ¿hay alguna variable que le indique qué se ha enviado a la cola de eventos?
tbarbe

20

Parece que algunas de las entidades discutidas (por ejemplo: 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 tratar de explicar el funcionamiento del modelo basado en eventos con la ayuda de un ejemplo abstracto, en un entorno UNIX abstracto, en el contexto de Node, a partir de hoy.

Perspectiva del programa:

  • El motor de script 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 vinculada 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 script. Operación vinculada a la CPU: ejecute en línea, las vinculadas de E / S, solicite a la maquinaria como se indicó anteriormente.
  • Cuando se completa la E / S, se vuelve a llamar a los oyentes.

La maquinaria de eventos anterior se llama marco de bucle de eventos libuv AKA. Node aprovecha esta biblioteca para implementar su modelo de programación dirigida por eventos.

Perspectiva del nodo:

  • Tener un hilo para alojar el tiempo de ejecución.
  • Recoge el guión de usuario.
  • Compilarlo en nativo [apalancamiento v8]
  • Cargue el binario y salte al punto de entrada.
  • El código compilado ejecuta las actividades vinculadas a la CPU en línea, utilizando primitivas de programación.
  • Muchos códigos relacionados con E / S y temporizadores tienen envolturas nativas. Por ejemplo, red I / O.
  • Por lo tanto, las llamadas de E / S se enrutan del script a los puentes de C ++, con el identificador de E / S y el controlador de finalización pasados ​​como argumentos.
  • El código nativo ejerce el bucle libuv. Adquiere el bucle, 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 del bucle libuv.
  • El código nativo vuelve a la secuencia de comandos: no se realiza ninguna E / S en este momento.
  • Los elementos anteriores se repiten muchas veces, hasta que se ejecute todo el código que no sea de E / S y se registre todo el código de E / S en el 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 al sistema operativo para obtener su operatividad.
  • Los 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 aún no ha escrito nada) se seguirán probando con el sistema operativo hasta que estén disponibles.
  • El bucle mantiene internamente un temporizador cada vez mayor. Cuando la aplicación solicita una devolución de llamada diferida (como setTimeout), este valor del temporizador interno se aprovecha para calcular el momento adecuado para disparar la devolución de llamada.

Si bien la mayoría de las funcionalidades se atienden de esta manera, algunas (versiones asíncronas) de las operaciones de archivo se llevan a cabo con la ayuda de hilos adicionales, bien integrados en el libuv. Si bien las operaciones de E / S de red pueden esperar a la espera de un evento externo, como el otro punto final que responde con datos, etc., las operaciones de archivo necesitan algo de trabajo del nodo mismo. Por ejemplo, si abre un archivo y espera 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 otras actividades en el programa y puede causar problemas visibles, ya que las operaciones de archivo son muy lentas en comparación con las actividades vinculadas a la CPU. Por lo tanto, los subprocesos de trabajo internos (configurables a través de la variable de entorno UV_THREADPOOL_SIZE) se emplean para operar en archivos,

Espero que esto ayude.


¿Cómo supiste estas cosas? ¿Puedes señalarme la fuente?
Suraj Jain

18

Una introducción a libuv

El proyecto node.js comenzó en 2009 como un entorno JavaScript desacoplado del navegador. El uso de Google V8 y de Marc Lehmann libev , Node.js un modelo combinado de E / S - evented - con un lenguaje que se adapta bien al estilo de programación; debido a la forma en que los navegadores le habían dado forma. A medida que node.js crecía en popularidad, era importante hacerlo funcionar en Windows, pero libev solo se ejecutaba en Unix. El equivalente de Windows de los mecanismos de notificación de eventos del kernel como kqueue o (e) poll es IOCP. libuv era una abstracción en torno a libev o IOCP dependiendo de la plataforma, proporcionando a los usuarios una API basada en libev. En la versión node-v0.9.0 de libuv libev se eliminó .

También una imagen que describe el bucle de eventos en Node.js por @ BusyRich


Actualización 05/09/2017

Según este bucle de eventos del documento 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 cuadro se denominará "fase" del bucle de eventos.

Resumen de fases

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

Entre cada ejecución del bucle de eventos, Node.js comprueba si está esperando alguna E / S asíncrona o temporizadores y se apaga limpiamente si no hay ninguno.


Ha citado " In the node-v0.9.0 version of libuv libev was removed", pero no hay una descripción al respecto en nodejs changelog. github.com/nodejs/node/blob/master/CHANGELOG.md . Y si se elimina libev, ¿cómo se realiza la E / S asíncrona en nodejs?
intekhab

@intekhab, según este enlace , creo que el libuv basado en libeio podría usarse como bucle de eventos en node.js.
zangw

@intekhab, creo que libuv está implementando todas las características relacionadas con E / S y sondeo. aquí revise este documento: docs.libuv.org/en/v1.x/loop.html
mohit kaushik

13

Hay un bucle de eventos en la arquitectura NodeJs.

Modelo de bucle de eventos de Node.js

Las aplicaciones de nodo se ejecutan en un modelo controlado por eventos de subproceso único. Sin embargo, Node implementa un grupo de subprocesos en segundo plano para que se pueda realizar el trabajo.

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

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

El bucle de eventos comienza a encontrar problemas cuando nuestras funciones de aplicación se bloquean en 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 E / S de bloqueo se realiza en un subproceso diferente en segundo plano.

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


8

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


1

Como principiante de JavaScript, también tuve la misma duda: ¿NodeJS contiene 2 bucles de eventos? Después de una larga investigación y discusión con uno de los contribuyentes de V8, obtuve los siguientes conceptos.

  • El bucle de eventos es un concepto abstracto fundamental del modelo de programación de JavaScript. Por lo tanto, el motor V8 proporciona una implementación predeterminada para el bucle de eventos, que los integradores (navegador, nodo) pueden reemplazar o ampliar . Ustedes pueden encontrar la implementación predeterminada V8 del bucle de eventos aquí
  • En NodeJS, solo existe un bucle de eventos , que proporciona el tiempo de ejecución del nodo. La implementación del bucle de eventos predeterminado V8 se reemplazó con la implementación del bucle de eventos NodeJS

0

La pbkdf2función tiene la implementación de JavaScript, pero en realidad delega todo el trabajo a realizar en el lado de C ++.

env->SetMethod(target, "pbkdf2", PBKDF2);
  env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
  env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
  env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
  NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
  NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
  NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
  NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
  NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
  env->SetMethod(target, "randomBytes", RandomBytes);
  env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
  env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
  env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
  env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
  env->SetMethodNoSideEffect(target, "getCurves", GetCurves);
  env->SetMethod(target, "publicEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_encrypt_init,
                                         EVP_PKEY_encrypt>);
  env->SetMethod(target, "privateDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_decrypt_init,
                                         EVP_PKEY_decrypt>);
  env->SetMethod(target, "privateEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_sign_init,
                                         EVP_PKEY_sign>);
  env->SetMethod(target, "publicDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_verify_recover_init,
                                         EVP_PKEY_verify_recover>);

recurso: https://github.com/nodejs/node/blob/master/src/node_crypto.cc

El módulo Libuv tiene otra responsabilidad que es relevante para algunas funciones muy particulares en la biblioteca estándar.

Para algunas llamadas de función de biblioteca estándar, el lado del Nodo C ++ y Libuv deciden hacer cálculos caros fuera del ciclo de eventos por completo.

En su lugar, hacen uso de algo llamado grupo de subprocesos, el grupo de subprocesos es una serie de cuatro subprocesos que se pueden usar para ejecutar tareas computacionalmente costosas como el pbkdf2 función.

Por defecto, Libuv crea 4 hilos en este grupo de hilos.

Además de los hilos utilizados en el bucle de eventos, hay otros cuatro hilos que pueden usarse para descargar cálculos costosos que deben ocurrir dentro de nuestra aplicación.

Muchas de las funciones incluidas en la biblioteca estándar de Nodos utilizan automáticamente este grupo de subprocesos. lospbkdf2 función es una de ellas.

La presencia de este grupo de subprocesos es muy significativa.

Por lo tanto, Node no es realmente un único subproceso, porque hay otros subprocesos que utiliza Node para realizar algunas tareas computacionalmente costosas.

Si el grupo de eventos fue responsable de realizar la tarea computacionalmente costosa, entonces nuestra aplicación Node no podría hacer nada más.

Nuestra CPU ejecuta todas las instrucciones dentro de un hilo una por una.

Al usar el grupo de subprocesos podemos hacer otras cosas dentro de un bucle de eventos mientras se realizan los cálculos.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.