Nodejs ·

nodejs事件循环

js的执行是事件循环模型,同样作为服务端的nodejs也是基于事件循环的事件模型,但是他又增加了一些非 IO 的异步 API: setTimeOut(), setInterval(), setImmediate() 以及 process.nextTick()。四种方法实现原理相似,但达到的效果略有区别。

nodejs事件循环

首先,我们需要了解node.js的基于事件循环的事件模型,正是因为它才使得node.js中回调函数十分普遍,也正是基于此,node.js实现了单线程高效的异步IO(这里说的单线程主要说的是执行javascript代码部分的线程,而异步IO部分node.js其实还是利用线程池去执行的)。

首先判断代码是同步还是异步,如果是同步则进入主线程,如果是异步代码就进入event table。异步任务在event table中注册函数,当异步代码达到执行条件时,就被推入到event queue事件队列当中。同步任务进入主线程后会一直执行,直到同步任务执行完毕,主线程才会出现空闲,此时会去事件队列中查找是否有可执行的异步任务,如果有就推入到主线程中开始执行。如此就完成了整个事件循环。

nodejs在启动时,他会创建一个类似于while(true)的结构,每次执行一次循环体称为一次tick,每个tick的过程就是查看是否有事件等待处理,如果有,则取出事件极其相关的回调函数并执行,然后执行下一次tick。

nodejs事件循环

区别

我们先来看一段代码

const EventEmitter = require('events')
class EE extends EventEmitter {}
const yy = new EE()
yy.on('event', () => console.log('粗大事啦'))
setTimeout(() => console.log('0 毫秒后到期的定时器回调'), 0)
setTimeout(() => console.log('100 毫秒后到期的定时器回调'), 100)
setImmediate(() => console.log('immediate 立即回调'))
process.nextTick(() => console.log('process.nextTick 的回调'))
Promise.resolve().then(() => {
  yy.emit('event')
  process.nextTick(() => console.log('process.nextTick第二次 的回调'))
  console.log('promise 第一次回调')
})
.then(() => console.log('promise 第二次回调'))

上述代码的执行结果如下图
nodejs事件循环

看了上面的结果,我们再来说一下为什么会这么执行。

上面的代码示例中我们没有提到setInterval(),因为这里面setTimeOut()与setInterval()除了执行频次外基本相同,都表示主线程执行完一定时间后立即执行,而setImmediate()与之十分相似,也表示主线程执行完成后立即执行。那么他们之间的区别是什么呢?

两者都代表主线程完成后立即执行,其执行结果是不确定的,可能是setTimeout回调函数执行结果在前,也可能是setImmediate回调函数执行结果在前,但setTimeout回调函数执行结果在前的概率更大些,这是因为他们采用的观察者不同,setTimeout采用的是类似IO观察者,但是不并不是IO观察者,个人感觉应该将其归类为timer观察者,但是官方没有这么个说法,setImmediate采用的是check观察者,而process.nextTick()采用的是idle观察者。

在每个tick中,如何判断是否有事件需要处理,于是引入了观察者的概念。每一个事件循环都有一个或多个观察者,判断是否有事件需要执行的过程其实就是想这些观察者询问是否有需要处理的事件。主要有以下几种

  • idle观察者:早已经等在那里的观察者,其执行顺序是主线程执行完成后立即执行,优先级最高,相当于插队到所有队列的最前端,process.nexTick()则采用此方法
  • I/O观察者:I/O观察者也就是I/O的回调事件,如网络,文件,数据库I/O等
  • check观察者:顾名思义,就是需要检查的观察者。

优先级则为:idle观察者>io观察者>check观察者

setImmediate() 和 setTimeout()的执行顺序并不是绝对的谁先谁后,下面来看看官方的介绍:

The order in which the timers are executed will vary depending on the context in which they are called. If both are called from within the main module, then timing will be bound by the performance of the process (which can be impacted by other applications running on the machine).
However, if you move the two calls within an I/O cycle, the immediate callback is always executed first:

普遍认为setTimeout的执行效率很低,所以在node里建议使用setImmediate或者process.nextTick。有很多人认为该函数的事件控制,是被维护在红黑树上,那么为了每次去找超时的回调必然是logn的复杂度。

参与评论