node事件循环
libuv
我们知道浏览器中的事件循环,异步任务是通过多线程调度由渲染主线程来完成的
而node底层用libuv来处理不同的异步任务,当收到请求或者文件读写的操作时,会让libuv去处理,libuv就会在线程池中调度空闲的线程去执行。
消息队列
与浏览器的事件循环一样,起初都是先执行同步代码,当同步代码执行完毕后,会查看消息队列中是否有待执行的任务。如果没有待执行的任务,主线程就会进入休眠状态。
这时还不算事件循环,事件循环是由libuv发起了异步任务,当到达可执行状态时,会被加入到不同的消息队列(宏任务)等待主线程去领取任务。
消息队列:
- 微任务
- process.nextTick、promise.then()、queueMicroTask
- 宏任务有8个队列
- timer(setTimeout、setInterval)
- pending callbacks阶段
- idle/prepare
- poll轮训( 网络请求、读写文件、IO操作)
- check(setImmediate)
- close callbacks(soket.on(’close’, …))
宏任务队列的执行顺序从上到下,每次必须把这个阶段的所有任务都执行完后,才会进入下一个阶段。比较值得注意的是:
timer队列:
用来处理定时器结束时的回调,事件循环时必须把每个阶段的任务都处理完尘才会进入下一个阶段,导致定时器回调任务被执行的时机肯定是靠后的,具体执行的时机无法预测
poll轮训队列
在处理完本阶段的所有任务后,如果后面阶段(check或timer)没有已经ready的任务,则一直等待,这个设计的目的是为了当网络请求或者IO操作时可以第一时间进行处理。
check队列
用来处理setImmediate定时器的回调,由于在poll轮训后面,所以比timer队列的执行效率高
event loop
- 执行同步代码,执行完毕后
- 优先查看消息队列中微队列是否有任务
- 清空所有微队列的任务,并优先执行process.nextTick
- 按照上面的阶段,先查看timer消息队列,将该队列的任务全部执行完毕后
- 查看微队列中是否有待执行的任务
- 依此执行所有微队列任务
- 没有继续下一个阶段
- 进入下一个pending callbacks队列
- 重复【5】
- 进入poll队列,执行所有任务后,
- 查看后续队列是否有ready的任务,如果没有则等待
- 如果有其他阶段的任务则继续下一个队列,执行下一个队列之前执行【5】
- 循环执行所有宏任务队列