node.js的异步I/O、事件驱动、单线程

nodejs的特点总共有以下几点

异步I/O(非阻塞I/O)

事件驱动

线程

擅长I/O密集型,不擅长CPU密集型

高并发

下面是一道很经典的面试题,描述了node的整体运行机制,相信很多人都碰到了。这道题背后的原理就是nodejs代码执行顺序

setTimeout(function() { console.log('4'); },0) setImmediate(function() { console.log('5'); }) let s = new Promise(function(resolve, reject) { console.log('2'); resolve(true) console.log('7') }) s.then(function() { console.log('3'); }) process.nextTick(function() { console.log('6') }) console.log('1'); // 我电脑的输出结果是 2、7、1、6、3、4、5 1. nodejs代码执行顺序(事件循环机制)

nodejs的运行机制: nodejs主线程主要起一个任务调度的作用。nodejs用一个主线程处理所有的请求, 将I/O操作交由底下的线程池处理;在所有主线程任务执行完成后,主线程处理事件队列。 所以在同步初始化代码执行完成后,nodejs会基于事件队列不停的做事件循环。事实上,nodejs运行环境 = 主线程(单线程,包括事件队列) + 线程池(工作线程池,执行其他工作-多线程)

node 的初始化

初始化 node 环境。

执行输入代码。

执行 process.nextTick 回调。

执行 microtasks。(Promise.then)

进入事件循环

进入 timers 阶段 (定时器阶段:本阶段执行已经安排的 setTimeout() 和 setInterval() 的回调函数。)

检查 timer 队列是否有到期的 timer 回调,如果有,将到期的 timer 回调按照 timerId 升序执行。

检查是否有 process.nextTick 任务,如果有,全部执行。

检查是否有microtask,如果有,全部执行。

退出该阶段。

进入pending IO callbacks阶段。(对某些系统操作(如 TCP 错误类型)执行回调)

检查是否有 pending 的 I/O 回调。如果有,执行回调。如果没有,退出该阶段。

检查是否有 process.nextTick 任务,如果有,全部执行。

检查是否有microtask,如果有,全部执行。

退出该阶段。

进入 idle,prepare 阶段:

仅系统内部使用。

进入 poll 阶段(检索新的 I/O 事件;执行与 I/O 相关的回调,除了定时器和关闭的回调函数,其余都在这里)

首先检查是否存在尚未完成的回调,如果存在,那么分两种情况。

第一种情况:

如果有可用回调(可用回调包含到期的定时器还有一些IO事件等),执行所有可用回调。

检查是否有 process.nextTick 回调,如果有,全部执行。

检查是否有 microtaks,如果有,全部执行。

退出该阶段。

第二种情况:

如果没有可用回调,执行下一步;

检查是否有 immediate 回调,如果有,退出 poll 阶段。如果没有,阻塞在此阶段,等待新的事件通知。

如果不存在尚未完成的回调,退出poll阶段。

进入 check 阶段。(setImmediate() 回调函数在这里执行)

如果有immediate回调,则执行所有immediate回调。

检查是否有 process.nextTick 回调,如果有,全部执行。

检查是否有 microtaks,如果有,全部执行。

退出 check 阶段

进入 closing 阶段。(检测关闭的回调函数,例如 xx.on('close'))

如果有immediate回调,则执行所有immediate回调。

检查是否有 process.nextTick 回调,如果有,全部执行。

检查是否有 microtaks,如果有,全部执行。

退出 closing 阶段

检查是否有活跃的 handles(定时器、IO等事件句柄)。

如果有,继续下一轮循环。

如果没有,结束事件循环,退出程序。

: 在主线程执行完和事件循环总共7个阶段,每一个阶段执行完都会调用一遍process.nextTick回调,一遍microtaks(promise);

2. setImmediate和process.nextTick和setTimeout

setImmediate(): 事件循环poll阶段执行完后执行setImmediate;

process.nextTick():主线程和事件循环每一阶段完成后都会调用;

setTimeout(): 最少经过n毫秒后执行的脚本,受到前一次事件循环时间影响,实际执行时间为>=n毫秒

** setTimeout和setImmediate执行顺序问题**

如果运行的是不属于 I/O 周期(即主模块)的以下脚本,则执行两个计时器的顺序是非确定性的,因为它受进程性能的约束;

如果你把这两个函数放入一个 I/O 循环内调用,setImmediate 总是被优先调用;I/O场景推荐使用setsetImmediate,因为setsetImmediate始终而且是立即执行

3. 对上题的理解

主线程中,console.log和promise的new方法在初始化主线程中执行,他们俩个的输出时间按照先上后下的顺序输出,他们两个执行完后会立即执行主线程的process.nextTick,然后执行promise.then方法,然后是进入事件队列中执行setTimeout和setImmediate。因为setTimeout的
'最少经过n毫秒后执行的脚本'特性,导致无法确定setTimeout和setImmediate的执行先后顺序,但如果是在回调函数中,则必然setImmediate先执行,因为事件循环的阶段中,setImmediate紧挨着回调函数之后执行,而setTimeout则在下次事件循环中执行。

4. 单线程和多线程

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyxggs.html