Node.js 是如何跑起来的( 四 )

 // resultrun main threadtick taskpromise resolvetimer tasktick task in timeouttick task in timeout->tickpromise resolve in timeouttick task in timeout promiseimmediate taskimeediate->tick task现在解读一下以上的执行流程:
NodeJS 在经过一系列初始化工作后,开始执行用户 JS 代码,解释执行过程中,分别把 setTimeout、setImmediate、Promise、nextTick 函数的回调插入 timer、immediate、microtask 和 nexttick 队列 。

  1. 执行主线程 console.log("run main thread"),打印 "run main thread"
  2. 进入 timer 阶段前,发现 nextTick 、promise 队列有任务如下:
nextTicks = [() => {console.log("tick task");}];microtasks = [() => {console.log('promise resolve');}];分别打印 "tick task" 以及 "promise resolve"
  1. 进入 timer 阶段,执行定时器回调,定时器回调中再次往 microtask 和 nextTick 插入新的任务如下:
nextTicks = [() => {console.log('tick task in timeout');process.nextTick(() => {console.log("tick task in timeout->tick");})}];microtasks = [() => {console.log('promise resolve in timeout');process.nextTick(() => {console.log('tick task in timeout promise');});}];打印主线程任务中的 "timer task",再进入下一阶段,发现 nextTicks 和 microtasks 队列为非空,执行微任务 。由于 nextTicks 优先级更高,先打印 "tick task in timeout",然后又往 nextTicks 插入 () => {console.log("tick task in timeout->tick")} ,继续执行 nextTicks 任务打印 "tick task in timeout->tick" 。
此时 nextTicks 队列已空,执行 miacrotasks 队列,打印 "promise resolve in timeout",此时又往 nextTicks 插入任务 () => {console.log('tick task in timeout promise')}?,继续执行 nextTick 任务,打印 "tick task in timeout promise" 。
进入 check 阶段(Immediate),为 nextTicks 添加 () => {console.log('imeediate->tick task') }?,主线程打印 "immediate task",进入下一阶段前先执行 nextTicks 任务,打印 'imeediate->tick task' 。
?拓展?setImmediate(() => console.log('this is set immediate 1'));setImmediate(() => console.log('this is set immediate 2'));setImmediate(() => console.log('this is set immediate 3'));setTimeout(() => console.log('this is set timeout 1'), 0);setTimeout(() => {console.log('this is set timeout 2');process.nextTick(() => console.log('this is process.nextTick added inside setTimeout'));}, 0);setTimeout(() => console.log('this is set timeout 3'), 0);setTimeout(() => console.log('this is set timeout 4'), 0);setTimeout(() => console.log('this is set timeout 5'), 0);process.nextTick(() => console.log('this is process.nextTick 1'));process.nextTick(() => {process.nextTick(console.log.bind(console, 'this is the inner next tick inside next tick'));});process.nextTick(() => console.log('this is process.nextTick 2'));process.nextTick(() => console.log('this is process.nextTick 3'));process.nextTick(() => console.log('this is process.nextTick 4'));I/O 模型得益于 Libuv 这一跨平台的高性能异步 I/O 库,使得 NodeJS 在处理 I/O 密集型任务上十分彰显优势 。下面结合不同的 I/O 模型,对比分析一下 NodeJS 目前工程实践所采用的 I/O 模型的优越性 。
首先理清一下阻塞和非阻塞、异步和同步的概念:
  • 阻塞和非阻塞
在应用程序通过 I/O 函数申请读写数据时,如果在数据就绪前进程一直在等待的,就是阻塞 I/O,即发起 I/O 请求时是阻塞的
  • 异步和同步
数据从内核缓冲区到到用户内存复制过程中,需要用户进程等待,就是同步 I/O,即实际的 I/O 读写是同步的
?同步阻塞?图片来源:https://www.51cto.com/article/693213.html
在网络编程中,当调用 recvfrom 获取客户端数据时,首先会阻塞进程,等待数据通过网卡到内核缓冲区;当数据就绪后,再同步等待指代数据从内核缓冲区拷贝到用户空间,此时用户进程再进行数据处理 。
同步阻塞 I/O 模型是最简单的 I/O 模型,好处是使用简单,通常在 fd 较少、数据就绪很快的场景,缺点是如果内核数据一直没准备好,则用户进程将会一直阻塞无法执行后续任务 。


推荐阅读