Node.js event loop mechanism

table of Contents

  • Microtask
  • Event loop mechanism
  • SetImmediate, setTimeout / setInterval and process.nextTick execution time comparison
  • Case Analysis
  • reference

1. Microtasks

Before talking about Node's event loop mechanism, let's add some explanation of "microtasks" in Node. The microtasks mentioned here are actually a general term, which includes two parts:

  • process.nextTick () Registered callback (nextTick task queue)
  • promise.then () registered callback (promise task queue)

When Node executes microtasks, it will give priority to the tasks in the nextTick task queue. After the execution, it will continue to execute the tasks in the promise task queue. So if the callback of process.nextTick and the callback of promise.then are in the same stage in the main thread or event loop, the callback of process.nextTick should take precedence over the callback of promise.then.


2. Event loop mechanism

Node event loop

As shown in the figure, it represents the entire process executed by Node. If any non-blocking asynchronous code is executed (timer creation, file reading and writing, etc.), it will enter the event loop. The event cycle is divided into six stages:

Since the Pending callbacks, Idle / Prepare, and Close callbacks stages are the three stages used internally by Node, here we mainly analyze the three stages of Timers, Poll, and Check that are more directly related to the developer code execution.


Timers (timer phase) : As can be seen from the figure, the first time you enter the event loop, it will start from the timer phase. At this stage, it will determine whether there is an expired timer callback (including setTimeout and setInterval). If it exists, all expired timer callbacks will be executed. After execution, if the corresponding microtask is triggered in the callback, all microtasks will be executed. , And then enter the Pending callbacks stage after executing the micro task.

Pending callbacks : I / O callbacks that are postponed to the next loop iteration (system-related callbacks).

Idle / Prepare : For internal use only. (Detailed)

Poll (Polling phase) :

When the callback queue is not empty:

The callback will be executed. If the corresponding microtask is triggered in the callback, the execution timing of the microtask here is different from other places. It will not wait until all callbacks have been executed, but will be executed for each callback. Corresponding microtasks. After executing all the return, it becomes the following situation.

When the callback queue is empty (no callbacks or all callbacks are executed):

However, if there are timers (setTimeout, setInterval, and setImmediate) that are not executed, the polling phase ends and the Check phase is entered. Otherwise, it will block and wait for any ongoing I / O operations to complete, and immediately execute the corresponding callbacks until all callbacks are completed.

Check (Query phase) : It will check if there is a callback related to setImmediate. If it exists, all callbacks will be executed. After the execution is completed, if the corresponding microtask is triggered in the callback, all microtasks will be executed, and then enter after completing the microtask. Close callbacks stage.

The callbacks use Close : Close to perform some callbacks, such as socket.on('close', ...)and so on.


Summary & Note:

  1. Each stage will have a FIFO callback queue. It will try to complete all callbacks in the current stage or reach system-related limits before entering the next stage.
  2. The timing of the microtasks executed in the Poll phase is different from the timing of the Timers & Check phases. The former executes the corresponding microtasks after each callback is executed, and the latter executes the corresponding microtasks after all callbacks are executed. .

3. Comparison of execution timing of setImmediate, setTimeout / setInterval and process.nextTick

setImmediate: triggers an asynchronous callback, which is executed immediately during the Check phase of the event loop.

setTimeout: trigger an asynchronous callback. When the timer expires, it is executed in the Timers phase of the event loop, and it is executed only once (can be canceled with clearTimeout).

setInterval: triggers an asynchronous callback, and each time the timer expires, a callback will be executed in the Timers phase of the event loop (can be canceled with clearInterval).

process.nextTick: triggers a microtask (asynchronous) callback, which can be executed in the main thread (mainline), and can be executed in a certain phase of the event sequence.


4. Case analysis

First group:

Compare setTimeout and setImmediate:

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

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

result:

setTimeout vs setImmediate

analysis:

From the output results, the output is uncertain, either "setTimeout" first, or "setImmediate" first. From the analysis of the event loop process, the event loop will start and will enter the Timers stage. Although the delay set by setTimeout is 0, it is actually 1, because the delay value range of setTimeout in Node must be in [1, 2 ^ 31 -1] In this range, otherwise it defaults to 1. Therefore, due to process performance constraints, the timer may not have expired when it reaches the Timers stage, so continue to the next process, so occasionally "setImmediate" output will appear in the front Case. If the delay of setTimeout is increased appropriately, such as 10, then basically "setImmediate" is output first.

Second Group:

Compare the execution order of the main thread (Mainline), Timers stage, Poll stage and Check stage and the corresponding microtask execution order:

 // 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')

result:

Callback execution in different phases and the sequence of corresponding microtask execution

analysis:

As shown in the mainline : you can see that the process.nextTick in the main thread is executed after the synchronization code is executed and before the event loop, which is in line with expectations.

As shown in the first timers : At this time, the event loop reaches the Timers stage for the first time, and the delay time of setInterval is up, so the callback is executed. Since there is no direct corresponding microtask triggered, it directly enters the following stage.

As shown in the first poll : At this time, the event loop reaches the Poll stage for the first time. Since the callback executed in the Timers stage earlier, two non-blocking I / O operations (readdir) are triggered. During this stage, the I / O After the operation is completed, the corresponding two callbacks are directly executed. It can be seen from the output that after each callback is executed, the corresponding microtask is executed. The microtask triggered in the microtask will continue to execute, and will not wait until all callbacks are executed before triggering the microtask, which is in line with expectations. After all callbacks are executed, because the timer is still scheduled, the Poll phase ends and enters the Check phase.

As shown in the first check at this time: At this time, the event loop reaches the Check stage for the first time, and directly triggers the execution of the corresponding two setImmediate. It can be seen from the output that the microtask is triggered after all callbacks are executed, which is in line with expectations. After executing the micro-tasks, enter the later stage.

As shown in the second timers at this time: At this time, the event loop reaches the Timers stage for the second time, and "timers: setInterval.setTimeout" is output first. Why? Do n’t forget that when the setInterval callback was executed for the first time, its internal setTimeout (..., 0) was actually executed once, but since it could not trigger the microtask, its callback was not executed, but entered In the later stage, wait until you come to the Timers stage again. According to the FIFO, the previous setTimeout callback is executed first, and then the setInterval callback is executed. Finally, after all the callbacks are completed, the microtask triggered in the setTimeout callback is executed. The last output is "timers microtasks: setInterval.setTimeout.process.nextTick", which is as expected (after all callbacks are executed, the corresponding microtasks are executed).

The following output is similar, so no more analysis is done.


5. Reference

Learn Node.js, Unit 5: Event Loop

Event loop, timer and process.nextTick ()

Guess you like

Origin www.cnblogs.com/forcheng/p/12723854.html