Mecanismo de bucle de eventos de Node.js

Directorio

  • Microtask
  • Mecanismo de bucle de eventos
  • SetImmediate, setTimeout / setInterval y process.nextTick comparación del tiempo de ejecución
  • Estudio de caso
  • Referencia

1. Microtask

Antes de hablar sobre el mecanismo de bucle de eventos de Node, agreguemos alguna explicación de "microtasks" en Node. Las microtasks mencionadas aquí son en realidad un término general, que incluye dos partes:

  • process.nextTick () Devolución de llamada registrada (cola de tareas nextTick)
  • promise.then () devolución de llamada registrada (cola de tareas de promesa)

Cuando Node ejecuta microtasks, dará prioridad a las tareas en la cola de tareas nextTick. Después de la ejecución, continuará ejecutando las tareas en la cola de tareas de promesa. Entonces, si la devolución de llamada de process.nextTick y la devolución de llamada de promise.then se encuentran en la misma etapa en el hilo principal o bucle de eventos, la devolución de llamada de process.nextTick debe tener prioridad sobre la devolución de llamada de promise.then.


2. Mecanismo de bucle de eventos

Nodo evento loop

Como se muestra en la figura, representa todo el proceso ejecutado por Node. Si se ejecuta un código asincrónico sin bloqueo (creación del temporizador, lectura y escritura de archivos, etc.), ingresará al bucle de eventos. El ciclo de eventos se divide en seis etapas:

Dado que las etapas de devolución de llamada Pendiente, Inactivo / Preparar y Cerrar son las tres etapas utilizadas internamente por Node, aquí analizamos principalmente las tres etapas de Timers, Poll y Check que están más directamente relacionadas con la ejecución del código del desarrollador.


Temporizadores (fase del temporizador) : como se puede ver en la figura, la primera vez que ingresa al bucle de eventos, comenzará desde la fase del temporizador. En esta etapa, determinará si hay una devolución de llamada de temporizador caducada (incluyendo setTimeout y setInterval). Si existe, se ejecutarán todas las devoluciones de llamada de temporizador caducadas. Después de la ejecución, si se activa la microtask correspondiente en la devolución de llamada, se ejecutarán todas las microtasks. , Y luego ingrese a la etapa Pendientes de devolución de llamada después de ejecutar la micro tarea.

Callbacks pendientes : Callbacks de E / S que se posponen a la siguiente iteración de bucle (callbacks relacionados con el sistema).

Inactivo / Preparado : solo para uso interno. (Detallado)

Encuesta (fase de votación) :

Cuando la cola de devolución de llamada no está vacía:

La devolución de llamada se ejecutará. Si se activa la microtask correspondiente en la devolución de llamada, el tiempo de ejecución de la microtask aquí es diferente de otros lugares. No esperará hasta que se hayan ejecutado todas las devoluciones de llamada. Microtasks correspondientes. Después de ejecutar toda la devolución, se convierte en la siguiente situación.

Cuando la cola de devolución de llamada está vacía (no se ejecutan devoluciones de llamada o se ejecutan todas las devoluciones de llamada):

Sin embargo, si hay temporizadores (setTimeout, setInterval y setImmediate) que no se ejecutan, la fase de sondeo finaliza y se ingresa la fase de verificación. De lo contrario, bloqueará y esperará a que se completen las operaciones de E / S en curso, e inmediatamente ejecutará las devoluciones de llamada correspondientes hasta que se completen todas las devoluciones de llamada.

Verificar (Fase de consulta) : Verificará si hay una devolución de llamada relacionada con setImmediate. Si existe, se ejecutarán todas las devoluciones de llamada. Después de la ejecución, si se activa la microtask correspondiente en la devolución de llamada, se ejecutarán todas las microtask. Cerrar las devoluciones de llamada.

Las devoluciones de llamada Close : Cerca de realizar algunas devoluciones de llamada, como por ejemplo socket.on('close', ...)y así sucesivamente.


Resumen y nota:

  1. Cada etapa tendrá una cola de devolución de llamada FIFO. Intentará completar todas las devoluciones de llamada en la etapa actual o alcanzar los límites relacionados con el sistema antes de ingresar a la siguiente etapa.
  2. La temporización de las microtask ejecutadas en la fase de Encuesta es diferente de la temporización de las fases Timers & Check. La primera ejecuta las microtasks correspondientes después de ejecutar cada devolución de llamada, y la segunda ejecuta las microtasks correspondientes después de que se ejecutan todas las devoluciones de llamada. .

3. Comparación del tiempo de ejecución de setImmediate, setTimeout / setInterval y process.nextTick

setImmediate: desencadena una devolución de llamada asincrónica, que se ejecuta inmediatamente durante la fase de verificación del bucle de eventos.

setTimeout: desencadena una devolución de llamada asincrónica. Cuando el temporizador expira, se ejecuta en la fase de temporizadores del bucle de eventos, y se ejecuta solo una vez (se puede cancelar con clearTimeout).

setInterval: desencadena una devolución de llamada asíncrona, y cada vez que expira el temporizador, se ejecuta una devolución de llamada en la fase de temporizadores del bucle de eventos (se puede cancelar con clearInterval).

process.nextTick: desencadena una devolución de llamada microtask (asíncrona), que se puede ejecutar en el hilo principal (línea principal), y se puede ejecutar en una cierta fase de la secuencia del evento.


4. Análisis de casos

El primer grupo:

Compare setTimeout y setImmediate:

// test.js
setTimeout(() => {
  console.log('setTimeout');
}, 0);

setImmediate(() => {
  console.log('setImmediate');
});

El resultado:

setTimeout vs setImmediate

Análisis:

Según los resultados de la salida, la salida es incierta, ya sea "setTimeout" primero o "setImmediate" primero. A partir del análisis del proceso del bucle de eventos, el bucle de eventos comenzará y entrará en la etapa de temporizadores. Aunque el retraso establecido por setTimeout es 0, en realidad es 1, porque el rango de valores de retraso de setTimeout en el nodo debe estar en [1, 2 ^ 31 -1] En este rango, de lo contrario, el valor predeterminado es 1. Por lo tanto, debido a las limitaciones de rendimiento del proceso, el temporizador puede no haber expirado cuando llega a la etapa de Temporizadores, así que continúe con el siguiente proceso, por lo que ocasionalmente aparecerá la salida "setImmediate" en el frente Situación Si el retraso de setTimeout se incrementa apropiadamente, como 10, entonces básicamente "setImmediate" sale primero.

El segundo grupo:

Compare el orden de ejecución del subproceso principal (Mainline), la etapa Timers, la etapa Poll y la etapa Check y el orden de ejecución microtask correspondiente:

 // test.js
 const fs = require('fs');

 console.log('mainline: start')
 process.nextTick(() => {
   console.log('mainline: ', 'process.nextTick\n')
 })

let counter = 0;
const interval = setInterval(() => {
  console.log('timers: setInterval.start ', counter)
  if(counter < 2) {
    setTimeout(() => {
      console.log('timers: setInterval.setTimeout')
      process.nextTick(() => {
        console.log('timers microtasks: ', 'setInterval.setTimeout.process.nextTick\n')
      })
    }, 0)

    fs.readdir('./', (err, files) => {
      console.log('poll: setInterval.readdir1')
      process.nextTick(() => {
        console.log('poll microtasks: ', 'setInterval.readdir1.process.nextTick')
        process.nextTick(() => {
          console.log('poll microtasks: ', 'setInterval.readdir1.process.nextTick.process.nextTick')
        })
      })
    })

    fs.readdir('./', (err, files) => {
      console.log('poll: setInterval.readdir2')
      process.nextTick(() => {
        console.log('poll microtasks: ', 'setInterval.readdir2.process.nextTick')
        process.nextTick(() => {
          console.log('poll microtasks: ', 'setInterval.readdir2.process.nextTick.process.nextTick\n')
        })
      })
    })

    setImmediate(() => {
      console.log('check: setInterval.setImmediate1')
      process.nextTick(() => {
        console.log('check microtasks: ', 'setInterval.setImmediate1.process.nextTick')
      })
    })

    setImmediate(() => {
      console.log('check: setInterval.setImmediate2')
      process.nextTick(() => {
        console.log('check microtasks: ', 'setInterval.setImmediate2.process.nextTick\n')
      })
    })
  } else {
    console.log('timers: setInterval.clearInterval')
    clearInterval(interval)
  }

  console.log('timers: setInterval.end ', counter)
  counter++;
}, 0);

 console.log('mainline: end')

El resultado:

Ejecución de devolución de llamada en diferentes fases y la secuencia de ejecución de microtask correspondiente

Análisis:

Como se muestra en la línea principal : puede ver que el process.nextTick en el hilo principal se ejecuta después de que se ejecuta el código de sincronización y antes del bucle de eventos, que está en línea con las expectativas.

Como se muestra en los primeros temporizadores : en este momento, el bucle de eventos llega a la etapa de Temporizadores por primera vez, y el tiempo de retraso de setInterval está activo, por lo que se ejecuta la devolución de llamada. Dado que no hay una microtask directa correspondiente, entra directamente en la siguiente etapa.

Como se muestra en la primera encuesta : en este momento, el bucle de eventos alcanza la etapa de Encuesta por primera vez. Dado que la devolución de llamada ejecutada en la etapa de Temporizadores anterior, se activan dos operaciones de E / S sin bloqueo (readdir). Durante esta etapa, la E / S Una vez completada la operación, las dos devoluciones de llamada correspondientes se ejecutan directamente. Se puede ver en el resultado que después de ejecutar cada devolución de llamada, se ejecuta la microtask correspondiente. La microtask activada en la microtask continuará ejecutándose, y no esperará hasta que se ejecuten todas las devoluciones de llamada antes de activar la microtask, que está en línea con las expectativas. Después de ejecutar todas las devoluciones de llamada, debido a que el temporizador aún está programado, la fase de Encuesta finaliza y entra en la fase de Verificación.

Como se muestra en la primera verificación en este momento: en este momento, el bucle de eventos alcanza la etapa de verificación por primera vez y activa directamente la ejecución de los dos setImmediate correspondientes. En la salida se puede ver que la microtask se activa después de que se ejecutan todas las devoluciones de llamada, lo que está en línea con las expectativas. Después de ejecutar las micro tareas, ingrese a la etapa posterior.

Como se muestra en los segundos temporizadores en este momento: en este momento, el bucle de eventos llega a la etapa de temporizadores por segunda vez, y "timers: setInterval.setTimeout" se emite primero. ¿Por qué? No olvide que cuando se ejecutó la devolución de llamada setInterval por primera vez, su setTimeout interno (..., 0) se ejecutó una vez, pero como no pudo activar la microtask, su devolución de llamada no se ejecutó, pero ingresó En la etapa posterior, espere hasta que vuelva a la etapa Timers. De acuerdo con FIFO, la devolución de llamada setTimeout anterior se ejecuta primero y luego se ejecuta la devolución de llamada setInterval. Finalmente, después de completar todas las devoluciones de llamada, se ejecuta la microtask activada en la devolución de llamada setTimeout. El último resultado es "temporizadores microtasks: setInterval.setTimeout.process.nextTick", que es como se esperaba (después de ejecutar todas las devoluciones de llamada, se ejecutan las microtasks correspondientes).

El siguiente resultado es similar, por lo que no se realiza más análisis.


5. Referencia

Learn Node.js, Unit 5: Event Loop

Bucle de eventos, temporizador y proceso.nextTick ()

Supongo que te gusta

Origin www.cnblogs.com/forcheng/p/12723854.html
Recomendado
Clasificación