理解 Node中的事件循环

理解 Node中的事件循环

很久没有写过关于node的文章了,于是即兴写一笔,是对官方文档的一个理解,也是对自己的学习做一个巩固。希望能够帮到大家

事件循环

node启动时会初始化一个事件循环,事件循环不会单独开一个线程,而是挂载到主线程。
node的事件循环与前端js的事件循环有些不一样,node事件循环分为6个阶段执行

   ┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<─────┤  connections, │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘

timers 阶段: 这个阶段执行setTimeout(callback) and setInterval(callback)预定的callback;

I/O callbacks 阶段: 执行除了 close事件的callbacks、被timers(定时器,setTimeout、setInterval等)设定的callbacks、setImmediate()设定的callbacks之外的callbacks;

idle, prepare 阶段: 仅node内部使用;

poll 阶段: 获取新的I/O事件, 适当的条件下node将阻塞在这里;

check 阶段: 执行setImmediate() 设定的callbacks;

close callbacks 阶段: 比如socket.on(‘close’, callback)的callback会在这个阶段执行.

每一个阶段有一个队列,event loop执行到该阶段时,会该阶段的队列里的所有callback,当队列callback为空时,或callback执行到上限的时,就跳至下一阶段进行执行

其中poll阶段除了执行当前阶段的队列里的所有callback之外,poll还负责检测是否有timer的callback,如timer到达时间,并且timer的callback还未执行,那么就循环至开头执行timer的callback

定时器的执行

先来看一个例子

const fs = require('fs');
const path = require('path')

function asyncSomething(cb){
    fs.readFile(path.join(__dirname, __filename),cb)
}

let timeoutSchedule = Date.now()
setTimeout(() => {
    let delay = Date.now() - timeoutSchedule;
    console.log(`timeout spend time ${delay} ms`)
},10)

asyncSomething(() => {
    let readFileTime = Date.now()
    console.log(`readFile spend time ${readFileTime - timeoutSchedule} ms`);
    while(Date.now() - timeoutSchedule < 20){}
})

输出

readFile spend time 1 ms
timeout spend time 21 ms

初始化后的event loop,首先进入timer,但是setTimeout指定的定时器10ms过后执行,因此继续进入下一阶段,一直到poll阶段,readFile执行1ms后完毕,readFile的callback延时20ms后,此时timer指定的定时器已经超时,马上循环到timer,执行定时器的callback,打印出来21ms。

然而如果定时器指定0ms后执行,那么event loop最开始进入timer就会执行timer指定的定时器的callback

比如将上述代码的setTimeout的延时时间改为0

const fs = require('fs');
const path = require('path')

function asyncSomething(cb){
    fs.readFile(path.join(__dirname, __filename),cb)
}

let timeoutSchedule = Date.now()
setTimeout(() => {
    let delay = Date.now() - timeoutSchedule;
    console.log(`timeout spend time ${delay} ms`)
},0)

asyncSomething(() => {
    let readFileTime = Date.now()
    console.log(`readFile spend time ${readFileTime - timeoutSchedule} ms`);
    while(Date.now() - timeoutSchedule < 20){}
})

输出

timeout spend time 1 ms
readFile spend time 3 ms

在event loop首次进入timer的时候就执行了timeout指定的callback,然后event loop往下倒poll阶段,执行readFile,但是由于timeout处于主模块下,系统的其他进程会影响到它,因此定时器不一定是进入初始化的event loop的timer,因此有时候,打印出来的结果会是:

readFile spend time 1 ms
timeout spend time 20 ms

Timeout和Immediate

在主模块下,同样受系统其他进程的影响,可能timeout不能进入第一次event loop的timer阶段,所以可能是处于check阶段的immediate先执行,也可能是处于timer阶段的timeout先执行。

setTimeout(() => console.log("timeout"),0)

setImmediate(() => console.log("immediate"))

输出的两种情况

timeout
immediate

或者

immediate
timeout

但是如果是处于其他的callback内,会是怎样的情况呢?

const fs = require('fs')
const path = require('path')

fs.readFile(path.join(__dirname,__filename),() => {
    setTimeout(() => console.log("timeout"),0)

    setImmediate(() => console.log("immediate"))
})

这样不管怎样,immediate总比timeout先执行

因为当readFile的回调触发时,此时处于event loop的poll阶段,由于check阶段设置有immediate,进入check阶段执行immediate,然后再回到timer阶段,执行timeout,由此输出

immediate
timeout

process.nextTick

这个函数可以很快速的把回调函数加入事件循环

值得注意的是,process.nextTick不属于event loop的任何阶段,它们会在两个阶段过渡的时候执行,即在第二个阶段执行之前执行。

const fs = require('fs')
const path = require('path')

fs.readFile(path.join(__dirname,__filename),() => {
    process.nextTick(() => console.log("nextTick1"))
    setTimeout(() => console.log("timeout"),0)

    setImmediate(() => {
        console.log("immediate")
        process.nextTick(() => console.log("nextTick4"))
        process.nextTick(() => console.log("nextTick5"))
    })
    process.nextTick(() => console.log("nextTick2"))
    process.nextTick(() => console.log("nextTick3"))
})

输出

nextTick1
nextTick2
nextTick3
immediate
nextTick4
nextTick5
timeout

首先readFile的callback触发的时候,处于eventLoop的poll阶段,poll到check的过渡阶段执行nextTick1,nextTick2,nextTick3,之后进入check阶段,触发immediate,发现timer有定时器需要触发,所以该由check阶段进入timer阶段,在过渡的过程中,nextTick4,nextTick5触发,最后打印出timeout

process.nextTick是immediate还未出现的产物,因此node的作者建议我们使用immediate,比如大量的process.nextTick,将使我们无法进入event loop的下一阶段

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值