Comprender el bucle de eventos

135

Estoy pensando en eso y esto es lo que se me ocurrió:

Digamos que tenemos un código como este:

console.clear();
console.log("a");
setTimeout(function(){console.log("b");},1000);
console.log("c");
setTimeout(function(){console.log("d");},0);

Entra una solicitud y el motor JS comienza a ejecutar el código anterior paso a paso. Las dos primeras llamadas son llamadas de sincronización. Pero cuando se trata de setTimeoutmétodo, se convierte en una ejecución asíncrona. Pero JS regresa de inmediato y continúa ejecutando, lo que se llama Non-Blockingo Async. Y continúa trabajando en otros, etc.

Los resultados de esta ejecución son los siguientes:

acdb

Entonces, básicamente, el segundo setTimeoutse terminó primero y su función de devolución de llamada se ejecuta antes que el primero y eso tiene sentido.

Estamos hablando de la aplicación de un solo subproceso aquí. JS Engine sigue ejecutando esto y, a menos que finalice la primera solicitud, no irá a la segunda. Pero lo bueno es que no esperará setTimeouta que se resuelvan las operaciones de bloqueo, por lo que será más rápido porque acepta las nuevas solicitudes entrantes.

Pero mis preguntas surgen en torno a los siguientes elementos:

# 1: Si estamos hablando de una aplicación de subproceso único, ¿qué mecanismo procesa setTimeoutsmientras el motor JS acepta más solicitudes y las ejecuta? ¿Cómo continúa el hilo único trabajando en otras solicitudes? Lo que funciona setTimeoutmientras otras solicitudes siguen llegando y se ejecutan.

# 2: Si estas setTimeoutfunciones se ejecutan detrás de escena mientras entran y se ejecutan más solicitudes, ¿qué lleva a cabo las ejecuciones asíncronas detrás de escena? ¿Qué es esta cosa de la que hablamos llamada EventLoop?

# 3: ¿ Pero no se debe poner todo el método EventLooppara que todo se ejecute y se llame al método de devolución de llamada? Esto es lo que entiendo cuando hablo de las funciones de devolución de llamada:

function downloadFile(filePath, callback)
{
  blah.downloadFile(filePath);
  callback();
}

Pero en este caso, ¿cómo sabe JS Engine si se trata de una función asincrónica para poder poner la devolución de llamada en la EventLoop? Perhaps something like thepalabra clave async` en C # o algún tipo de atributo que indique que el método que JS Engine adoptará es un método asincrónico? y debe ser tratado en consecuencia.

# 4: Pero un artículo dice bastante contrario a lo que estaba adivinando sobre cómo podrían estar funcionando las cosas:

Event Loop es una cola de funciones de devolución de llamada. Cuando se ejecuta una función asíncrona, la función de devolución de llamada se inserta en la cola. El motor de JavaScript no comienza a procesar el bucle de eventos hasta que se ejecuta el código después de que se haya ejecutado una función asíncrona.

# 5: Y aquí está esta imagen que podría ser útil, pero la primera explicación en la imagen dice exactamente lo mismo mencionado en la pregunta número 4:

ingrese la descripción de la imagen aquí

Entonces, mi pregunta aquí es obtener algunas aclaraciones sobre los elementos enumerados anteriormente.

Tarik
fuente
1
Los hilos no son la metáfora correcta para manejar esos problemas. Piensa en eventos.
Denys Séguret
1
@dystroy: Sería bueno ver una muestra de código para ilustrar esa metáfora del evento en JS.
Tarik
No veo cuál es exactamente tu pregunta aquí.
Denys Séguret
1
@distroy: ¿Mi pregunta aquí es obtener algunas aclaraciones sobre los elementos enumerados anteriormente?
Tarik
2
El nodo no es de un solo subproceso, pero eso no es importante para usted (aparte del hecho de que logra hacer otras cosas mientras se ejecuta su código de usuario). Solo se ejecuta una devolución de llamada como máximo en su código de usuario a la vez.
Denys Séguret

Respuestas:

85

1: Si estamos hablando de una aplicación de subproceso único, ¿qué procesos establece setTimeouts mientras que el motor JS acepta más solicitudes y las ejecuta? ¿No es ese hilo único continuará trabajando en otras solicitudes? Entonces, ¿quién va a seguir trabajando en setTimeout mientras otras solicitudes siguen llegando y se ejecutan?

Solo hay 1 subproceso en el proceso de nodo que realmente ejecutará el JavaScript de su programa. Sin embargo, dentro del nodo en sí, en realidad hay varias operaciones de manejo de subprocesos del mecanismo de bucle de eventos, y esto incluye un grupo de subprocesos de E / S y algunos otros. La clave es que el número de estos subprocesos no corresponde al número de conexiones concurrentes que se manejan como lo harían en un modelo de concurrencia de subprocesos por conexión.

Ahora sobre "ejecutar setTimeouts", cuando invocas setTimeout, todo lo que hace el nodo es básicamente actualizar una estructura de datos de funciones que se ejecutarán en el futuro. Básicamente tiene un montón de colas de cosas que necesita hacer y cada "tic" del bucle de eventos selecciona uno, lo elimina de la cola y lo ejecuta.

Una cosa clave para entender es que el nodo se basa en el sistema operativo para la mayor parte del trabajo pesado. Por lo tanto, el sistema operativo realiza un seguimiento de las solicitudes de red entrantes y, cuando el nodo está listo para manejar una, solo utiliza una llamada del sistema para solicitarle una solicitud de red con datos listos para ser procesados. Gran parte del nodo de "trabajo" de E / S es "Oye OS, ¿tiene una conexión de red con datos listos para leer?" o "Hola OS, ¿alguna de mis llamadas pendientes al sistema de archivos tiene datos listos?". Basado en su algoritmo interno y diseño del motor de bucle de eventos, el nodo seleccionará un "tic" de JavaScript para ejecutar, ejecutarlo y luego repetir el proceso nuevamente. Eso es lo que se entiende por el bucle de eventos. El nodo básicamente determina en todo momento "¿cuál es el próximo bit de JavaScript que debo ejecutar?", Luego lo ejecuta.setTimeouto process.nextTick.

2: Si estos setTimeout se ejecutarán detrás de escena mientras llegan y entran y se ejecutan más solicitudes, lo que lleva a cabo las ejecuciones asíncronas detrás de escena es que ¿estamos hablando de EventLoop?

No se ejecuta JavaScript detrás de escena. Todo el JavaScript en su programa se ejecuta al frente y al centro, uno a la vez. Lo que sucede detrás de escena es que el sistema operativo maneja la E / S y el nodo espera a que esté listo y el nodo administra su cola de javascript esperando para ejecutarse.

3: ¿Cómo puede saber JS Engine si es una función asíncrona para poder colocarla en EventLoop?

Hay un conjunto fijo de funciones en el núcleo del nodo que son asíncronas porque hacen llamadas al sistema y el nodo sabe cuáles son porque tienen que llamar al sistema operativo o C ++. Básicamente, todas las interacciones IO de red y sistema de archivos, así como las interacciones de procesos secundarios, serán asíncronas y la ÚNICA forma en que JavaScript puede hacer que el nodo ejecute algo de forma asincrónica es invocando una de las funciones asíncronas proporcionadas por la biblioteca central de nodos. Incluso si está utilizando un paquete npm que define su propia API, para producir el bucle de eventos, eventualmente el código del paquete npm llamará a una de las funciones asíncronas del núcleo del nodo y es cuando el nodo sabe que la marca está completa y puede iniciar el evento algoritmo de bucle de nuevo.

4 El bucle de eventos es una cola de funciones de devolución de llamada. Cuando se ejecuta una función asíncrona, la función de devolución de llamada se inserta en la cola. El motor de JavaScript no comienza a procesar el bucle de eventos hasta que se ejecuta el código después de que se haya ejecutado una función asíncrona.

Sí, esto es cierto, pero es engañoso. La clave es que el patrón normal es:

//Let's say this code is running in tick 1
fs.readFile("/home/barney/colors.txt", function (error, data) {
  //The code inside this callback function will absolutely NOT run in tick 1
  //It will run in some tick >= 2
});
//This code will absolutely also run in tick 1
//HOWEVER, typically there's not much else to do here,
//so at some point soon after queueing up some async IO, this tick
//will have nothing useful to do so it will just end because the IO result
//is necessary before anything useful can be done

Entonces, sí, podría bloquear totalmente el bucle de eventos simplemente contando los números de Fibonacci sincrónicamente todos en la memoria, todo en el mismo tic, y sí, eso congelaría totalmente su programa. Es concurrencia cooperativa. Cada tick de JavaScript debe producir el bucle de eventos dentro de un período de tiempo razonable o la arquitectura general falla.

Peter Lyons
fuente
1
Digamos que tengo una cola que tardará 1 minuto en ejecutarse en el servidor, y lo primero fue una función asíncrona que finalizó después de 10 segundos. ¿Irá al final de la cola o se empujará a la línea en el momento en que esté listo?
ilyo
44
En general, irá al final de la cola, pero la semántica de process.nextTickvs setTimeoutvs setImmediatees sutilmente diferente, aunque realmente no debería tener que preocuparse. Tengo una publicación de blog llamada setTimeout y amigos que entra en más detalles.
Peter Lyons
¿Puedes por favor elaborar? Digamos que tengo dos devoluciones de llamada y la primera tiene un método changeColor con un tiempo de ejecución de 10 ms y un setTimeout de 1 minuto y la segunda tiene un método changeBackground con un tiempo de ejecución de 50 ms con un setTimeout de 10 segundos. Siento que changeBackground estará primero en la cola y el changeColor será el siguiente. Después de eso, el bucle de eventos selecciona los métodos de forma sincrónica. Estoy en lo cierto?
SheshPai
1
@SheshPai es demasiado confuso para todos discutir el código cuando está escrito en párrafos de inglés. Simplemente publique una nueva pregunta con un fragmento de código para que las personas puedan responder según el código en lugar de una descripción del código, lo que deja mucha ambigüedad.
Peter Lyons
youtube.com/watch?v=QyUFheng6J0&spfreload=5 esta es otra buena explicación de JavaScript Engine
Mukesh Kumar
65

Hay un fantástico video tutorial de Philip Roberts, que explica el bucle de eventos de JavaScript de la manera más simple y conceptual. Todos los desarrolladores de JavaScript deberían echar un vistazo.

Aquí está el enlace del video en Youtube.

sam100rav
fuente
16
Lo vi y de hecho fue la mejor explicación.
Tarik
1
Una visita obligada para los fanáticos y entusiastas de JavaScript.
Nirus
1
este video cambia mi vida ^^
HuyTran
1
vino vagando aquí ... y esta es una de las mejores explicaciones que obtuve ... gracias por compartir ...: D
RohitS
1
Eso fue una
revelación
11

No piense que el proceso del host es de un solo subproceso, no lo son. Lo que es de un solo subproceso es la parte del proceso de host que ejecuta su código javascript.

Excepto para los trabajadores de fondo , pero estos complican el escenario ...

Por lo tanto, todo su código js se ejecuta en el mismo subproceso, y no hay posibilidad de que obtenga dos porciones diferentes de su código js para que se ejecuten simultáneamente (por lo tanto, no obtiene una pesadilla de concurrencia para administrar).

El código js que se está ejecutando es el último código que el proceso del host recogió del bucle de eventos. Básicamente, en su código puede hacer dos cosas: ejecutar instrucciones síncronas y programar funciones para que se ejecuten en el futuro, cuando ocurran algunos eventos.

Aquí está mi representación mental (cuidado: ¡es solo eso, no sé los detalles de implementación del navegador!) De su código de ejemplo:

console.clear();                                   //exec sync
console.log("a");                                  //exec sync
setTimeout(                //schedule inAWhile to be executed at now +1 s 
    function inAWhile(){
        console.log("b");
    },1000);    
console.log("c");                                  //exec sync
setTimeout(
    function justNow(){          //schedule justNow to be executed just now
        console.log("d");
},0);       

Mientras se ejecuta el código, otro hilo en el proceso del host realiza un seguimiento de todos los eventos del sistema que están ocurriendo (clics en la interfaz de usuario, archivos leídos, paquetes de redes recibidos, etc.)

Cuando se completa el código, se elimina del bucle de eventos y el proceso del host vuelve a verificarlo para ver si hay más código para ejecutar. El bucle de eventos contiene dos controladores de eventos más: uno que se ejecutará ahora (la función justNow) y otro en un segundo (la función inAWhile).

El proceso de host ahora intenta hacer coincidir todos los eventos ocurridos para ver si hay controladores registrados para ellos. Descubrió que el evento que justNow está esperando ha sucedido, por lo que comienza a ejecutar su código. Cuando la función justNow sale, verifica el bucle de eventos otra vez, buscando controladores en los eventos. Suponiendo que ha pasado 1 s, ejecuta la función inAWhile, y así sucesivamente ...

Andrea Parodi
fuente