Comprender el bucle de eventos

Resuelto Tarik asked hace 10 años • 4 respuestas

Lo estoy pensando y esto es lo que se me ocurrió:

Veamos este código a continuación:

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

Llega 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 inmediatamente y continúa ejecutándose, lo que se llama Non-Blockingo Async. Y sigue trabajando en otros etc.

El resultado de esta ejecución es el siguiente:

acdb

Básicamente, el segundo setTimeoutterminó primero y su función de devolución de llamada se ejecuta antes que el primero y eso tiene sentido.

Estamos hablando de una aplicación de un solo subproceso aquí. JS Engine sigue ejecutando esto y, a menos que finalice la primera solicitud, no pasará a la segunda. Pero lo bueno es que no esperará a que se setTimeoutresuelvan 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 un solo subproceso, ¿qué mecanismo procesa setTimeoutsmientras el motor JS acepta más solicitudes y las ejecuta? ¿Cómo continúa funcionando el hilo único 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? ¿ Cómo se llama esto de lo que hablamos EventLoop?

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

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

Pero en este caso, ¿cómo sabe JS Engine si es una función asíncrona para poder colocar la devolución de llamada en el archivo EventLoop? Quizás algo como la asyncpalabra clave en C# o algún tipo de atributo que indique que el método que adoptará JS Engine es un método asíncrono y debe tratarse en consecuencia.

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

El 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 envía a la cola. El motor de JavaScript no comienza a procesar el bucle de eventos hasta que se haya ejecutado 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 que se menciona 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 avatar Feb 06 '14 22:02 Tarik
Aceptado

1: Si estamos hablando de una aplicación de un solo subproceso, ¿qué procesa setTimeouts mientras el motor JS acepta más solicitudes y las ejecuta? ¿No seguirá ese hilo único trabajando en otras solicitudes? Entonces, ¿quién seguirá trabajando en setTimeout mientras siguen llegando y ejecutadas otras solicitudes?

Solo hay 1 subproceso en el proceso del nodo que realmente ejecutará el JavaScript de su programa. Sin embargo, dentro del propio nodo, en realidad hay varios subprocesos que manejan la operación del mecanismo de bucle de eventos, y esto incluye un grupo de subprocesos IO y algunos otros. La clave es que la cantidad de estos subprocesos no corresponde a la cantidad de conexiones simultáneas que se manejan como lo harían en un modelo de concurrencia de subprocesos por conexión.

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

Un aspecto clave que hay que entender es que el nodo depende del sistema operativo para la mayor parte del trabajo pesado. Por lo tanto, las solicitudes de red entrantes son en realidad rastreadas por el propio sistema operativo y cuando el nodo está listo para manejar una, simplemente usa una llamada al sistema para solicitarle al sistema operativo una solicitud de red con datos listos para ser procesados. Gran parte del nodo de "trabajo" de IO es "Hola, sistema operativo, ¿tienes 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?". Según su algoritmo interno y el diseño del motor de bucle de eventos, node seleccionará un "tic" de JavaScript para ejecutarlo, lo ejecutará y luego repetirá el proceso nuevamente. Eso es lo que se entiende por bucle de eventos. Básicamente, Node determina en todo momento "¿cuál es el siguiente fragmento de JavaScript que debo ejecutar?" y luego lo ejecuta. Esto tiene en cuenta en qué IO se ha completado el sistema operativo y las cosas que se han puesto en cola en JavaScript mediante llamadas a setTimeouto process.nextTick.

2: Si estos setTimeout se ejecutarán detrás de escena mientras entran, entran y se ejecutan más solicitudes, ¿lo que lleva a cabo las ejecuciones asincrónicas detrás de escena es de ese EventLoop del que estamos hablando?

No se ejecuta ningún JavaScript detrás de escena. Todo el JavaScript de 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 IO y el nodo espera a que esté listo y el nodo administra su cola de JavaScript esperando para ejecutarse.

3: ¿Cómo puede JS Engine saber 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 realizan llamadas al sistema y el nodo sabe cuáles son porque tienen que llamar al sistema operativo o a C++. Básicamente, todas las 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 asíncrona es invocando una de las funciones asíncronas proporcionadas por la biblioteca central del nodo. Incluso si está utilizando un paquete npm que define su propia API, para generar el bucle de eventos, eventualmente el código de ese paquete npm llamará a una de las funciones asíncronas del núcleo del nodo y ahí es cuando el nodo sabe que el tick está completo y puede iniciar el evento. algoritmo de bucle nuevamente.

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 envía a la cola. El motor de JavaScript no comienza a procesar el bucle de eventos hasta que se haya ejecutado 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ías bloquear totalmente el bucle de eventos simplemente contando los números de Fibonacci sincrónicamente, todos en la memoria, en el mismo tick, y sí, eso congelaría totalmente tu programa. Es concurrencia cooperativa. Cada tic de JavaScript debe generar el bucle de eventos dentro de un período de tiempo razonable o la arquitectura general fallará.

Peter Lyons avatar Feb 06 '2014 16:02 Peter Lyons

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

Excepto por los trabajadores en segundo plano , pero estos complican el escenario...

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

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

Aquí está mi representación mental (cuidado: es sólo eso, ¡no conozco 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 su código, otro subproceso en el proceso del host realiza un seguimiento de todos los eventos del sistema que ocurren (clics en la interfaz de usuario, archivos leídos, paquetes de red recibidos, etc.)

Cuando se completa su 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 dentro de un segundo (la función inAWhile).

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

Andrea Parodi avatar Feb 06 '2014 16:02 Andrea Parodi

El bucle de eventos tiene una tarea simple: monitorear la pila de llamadas , la cola de devolución de llamadas y la cola de microtareas . Si la pila de llamadas está vacía, el bucle de eventos tomará el primer evento de la cola de microtareas y luego de la cola de devolución de llamadas y lo enviará a la pila de llamadas, que efectivamente lo ejecuta. Esta iteración se denomina tick en el bucle de eventos.

Como la mayoría de los desarrolladores saben, el hecho de que Javascript sea de un solo subproceso significa que dos declaraciones en JavaScript no se pueden ejecutar en paralelo, lo cual es correcto. La ejecución ocurre línea por línea, lo que significa que cada declaración de JavaScript es sincrónica y bloqueante. Pero hay una manera de ejecutar su código de forma asincrónica, si usa la función setTimeout(), una API web proporcionada por el navegador, que garantiza que su código se ejecute después de un tiempo específico (en milisegundos).

Ejemplo:

console.log("Start");

setTimeout(function cbT(){
console.log("Set time out");
},5000);

fetch("http://developerstips.com/").then(function cbF(){
console.log("Call back from developerstips");
});

// Millions of line code
// for example it will take 10000 millisecond to execute

console.log("End");

setTimeout toma una función de devolución de llamada como primer parámetro y el tiempo en milisegundos como segundo parámetro. Después de la ejecución de la declaración anterior en la consola del navegador, se imprimirá

Start
End
Call back from developerstips
Set time out

Nota : Su código asincrónico se ejecuta después de que todo el código sincrónico haya terminado de ejecutarse.

Comprender cómo se ejecuta el código línea por línea

El motor JS ejecuta la primera línea e imprimirá "Inicio" en la consola

En la segunda línea ve la función setTimeout denominada cbT, y el motor JS empuja la función cbT a la cola de devolución de llamada.

Después de esto, el puntero saltará directamente a la línea número 7 y allí verá la promesa y el motor JS enviará la función cbF a la cola de microtareas.

Luego ejecutará millones de líneas de código y al final imprimirá "Fin".

Después de que finalice la ejecución del hilo principal, el bucle de eventos primero verificará la cola de microtareas y luego devolverá la llamada a la cola. En nuestro caso, toma la función cbF de la cola de microtareas y la inserta en la pila de llamadas, luego seleccionará la función cbT de la cola de devolución de llamadas y la insertará en la pila de llamadas.

ingrese la descripción de la imagen aquí

Srikrushna avatar May 13 '2021 20:05 Srikrushna