写这篇文章的目的是将自己对该文章的理解做一个记录,官方文档链接The Node.js Event Loop, Timers, and process.nextTick()

文章内容可能有错误理解的地方,希望能和大家探讨一下,欢迎批评指正!

Node.js Event Loop 的理解 Timers,process.nextTick()

Event Loop的解释

英文原文: When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.

当Node.js启动时会初始化event loop, 每一个event loop都会包含按如下顺序六个循环阶段,

   ┌───────────────────────┐
┌─>│ 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会在这个阶段执行.

每一个阶段都有一个装有callbacks的fifo queue(队列),当event loop运行到一个指定阶段时, node将执行该阶段的fifo queue(队列),当队列callback执行完或者执行callbacks数量超过该阶段的上限时, event loop会转入下一下阶段.

注意上面六个阶段都不包括 process.nextTick()

poll阶段

poll阶段是衔接整个event loop各个阶段比较重要的阶段,为了便于后续例子的理解,本文和原文的介绍顺序不一样,本文先讲这个阶段;

在node.js里,任何异步方法(除timer,close,setImmediate之外)完成时,都会将其callback加到poll queue里,并立即执行。

poll 阶段有两个主要的功能:

  1. 处理poll队列(poll quenue)的事件(callback);
  2. 执行timers的callback,当到达timers指定的时间时;

如果event loop进入了 poll阶段,且代码未设定timer,将会发生下面情况:

  • 如果poll queue不为空,event loop将同步的执行queue里的callback,直至queue为空,或执行的callback到达系统上限;
  • 如果poll queue为空,将会发生下面情况:
    • 如果代码已经被setImmediate()设定了callback, event loop将结束poll阶段进入check阶段,并执行check阶段的queue (check阶段的queue是 setImmediate设定的)
    • 如果代码没有设定setImmediate(callback),event loop将阻塞在该阶段等待callbacks加入poll queue;

如果event loop进入了 poll阶段,且代码设定了timer:

  • 如果poll queue进入空状态时(即poll 阶段为空闲状态),event loop将检查timers,如果有1个或多个timers时间时间已经到达,event loop将按循环顺序进入 timers 阶段,并执行timer queue.

以上便是整个event loop时间循环的各个阶段运行机制,有了这层理解,我们来看几个例子

  • 注意,例子中给出的时间在不同机器下和同一机器下不同执行时刻,其值都会有差异。

example 1

var fs = require('fs');

function someAsyncOperation (callback) {
// 花费2毫秒
fs.readFile(__dirname + '/' + __filename, callback);
} var timeoutScheduled = Date.now();
var fileReadTime = 0; setTimeout(function () {
var delay = Date.now() - timeoutScheduled;
console.log('setTimeout: ' + (delay) + "ms have passed since I was scheduled");
console.log('fileReaderTime',fileReadtime - timeoutScheduled);
}, 10); someAsyncOperation(function () {
fileReadtime = Date.now();
while(Date.now() - fileReadtime < 20) { }
});

结果: 先执行someAsyncOperation的callback,再执行setTimeout callback

-> node eventloop.js
setTimeout: 22ms have passed since I was scheduled
fileReaderTime 2

解释: 当时程序启动时,event loop初始化:

  1. timer阶段(无callback到达,setTimeout需要10毫秒)
  2. i/o callback阶段,无异步i/o完成
  3. 忽略
  4. poll阶段,阻塞在这里,当运行2ms时,fs.readFile完成,将其callback加入 poll队列,并执行callback, 其中callback要消耗20毫秒,等callback之行完,poll处于空闲状态,由于之前设定了timer,因此检查timers,发现timer设定时间是20ms,当前时间运行超过了该值,因此,立即循环回到timer阶段执行其callback,因此,虽然setTimeout的20毫秒,但实际是22毫秒后执行。

example 2

var fs = require('fs');

function someAsyncOperation (callback) {
var time = Date.now();
// 花费9毫秒
fs.readFile('/path/to/xxxx.pdf', callback);
} var timeoutScheduled = Date.now();
var fileReadTime = 0;
var delay = 0; setTimeout(function () {
delay = Date.now() - timeoutScheduled;
}, 5); someAsyncOperation(function () {
fileReadtime = Date.now();
while(Date.now() - fileReadtime < 20) { }
console.log('setTimeout: ' + (delay) + "ms have passed since I was scheduled");
console.log('fileReaderTime',fileReadtime - timeoutScheduled);
});

结果:setTimeout callback先执行,someAsyncOperation callback后执行

-> node eventloop.js
setTimeout: 7ms have passed since I was scheduled
fileReaderTime 9

解释: 当时程序启动时,event loop初始化:

  1. timer阶段(无callback到达,setTimeout需要10毫秒)
  2. i/o callback阶段,无异步i/o完成
  3. 忽略
  4. poll阶段,阻塞在这里,当运行5ms时,poll依然空闲,但已设定timer,且时间已到达,因此,event loop需要循环到timer阶段,执行setTimeout callback,由于从poll --> timer中间要经历check,close阶段,这些阶段也会消耗一定时间,因此执行setTimeout callback实际是7毫秒 然后又回到poll阶段等待异步i/o完成,在9毫秒时fs.readFile完成,其callback加入poll queue并执行。

setTimeout 和 setImmediate

二者非常相似,但是二者区别取决于他们什么时候被调用.

  • setImmediate 设计在poll阶段完成时执行,即check阶段;
  • setTimeout 设计在poll阶段为空闲时,且设定时间到达后执行;但其在timer阶段执行

其二者的调用顺序取决于当前event loop的上下文,如果他们在异步i/o callback之外调用,其执行先后顺序是不确定的

setTimeout(function timeout () {
console.log('timeout');
},0); setImmediate(function immediate () {
console.log('immediate');
});
$ node timeout_vs_immediate.js
timeout
immediate $ node timeout_vs_immediate.js
immediate
timeout

关于这点的原因,笔者目前还未弄清楚。。。其原因读者可去看@hyj1991 的留言,讲解非常清晰

但当二者在异步i/o callback内部调用时,总是先执行setImmediate,再执行setTimeout

var fs = require('fs')

fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout')
}, 0)
setImmediate(() => {
console.log('immediate')
})
})
$ node timeout_vs_immediate.js
immediate
timeout

理解了event loop的各阶段顺序这个例子很好理解: 因为fs.readFile callback执行完后,程序设定了timer 和 setImmediate,因此poll阶段不会被阻塞进而进入check阶段先执行setImmediate,后进入timer阶段执行setTimeout

process.nextTick()

千呼万唤始出来,终于到了讲process.nextTick()的时候,来来来喝口水休息休息。

注意:本文讲解的process.nextTick是基于v0.10及以上版本

process.nextTick()不在event loop的任何阶段执行,而是在各个阶段切换的中间执行,即从一个阶段切换到下个阶段前执行。

var fs = require('fs');

fs.readFile(__filename, () => {
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
process.nextTick(()=>{
console.log('nextTick3');
})
});
process.nextTick(()=>{
console.log('nextTick1');
})
process.nextTick(()=>{
console.log('nextTick2');
})
});
-> node eventloop.js
nextTick1
nextTick2
setImmediate
nextTick3
setTimeout

从poll —> check阶段,先执行process.nextTick, nextTick1 nextTick2 然后进入check,setImmediate, setImmediate 执行完setImmediate后,出check,进入close callback前,执行process.nextTick nextTick3 最后进入timer执行setTimeout setTimeout

process.nextTick()是node早期版本无setImmediate时的产物,node作者推荐我们尽量使用setImmediate。

example process.nextTick()

当递归调用process.nextTick时,即使fs.readFile完成,其callback无机会执行

var fs = require('fs');
var starttime = Date.now();
var endtime = 0; fs.readFile(__filename, () => {
endtime = Date.now();
console.log('finish reading time:',endtime - starttime);
}); var index = 0; function nextTick () {
if (index > 1000) return;
index++;
console.log('nextTick');
process.nextTick(nextTick);
} nextTick();
-> node eventloop.js
nextTick
nextTick
...
nextTick
nextTick
finish reading time: 246

example setImmediate

将process.nextTick替换成setImmediate后,由于setImmediate只在check阶段执行,那么所有的callback都有机会执行。

var fs = require('fs');

fs.readFile(__filename, () => {
console.log('finish reading');
}); var index = 0; function Immediate () {
if (index > 100) return;
index++;
console.log('setImmediate');
setImmediate(Immediate);
} Immediate()
-> node eventloop.js
setImmediate
setImmediate
setImmediate
setImmediate
finish reading time: 19
...
setImmediate
setImmediate

Node.js Event Loop 的理解 Timers,process.nextTick()的更多相关文章

  1. The Node.js Event Loop, Timers, and process.nextTick() Node.js事件循环,定时器和process.nextTick()

    个人翻译 原文:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/ The Node.js Event Loop, Ti ...

  2. The Node.js Event Loop, Timers, and process.nextTick()

    The Node.js Event Loop, Timers, and process.nextTick() | Node.js https://nodejs.org/uk/docs/guides/e ...

  3. Node.js event loop 和 JS 浏览器环境下的事件循环的区别

    Node.js  event loop 和 JS 浏览器环境下的事件循环的区别: 1.线程与进程: JS 是单线程执行的,指的是一个进程里只有一个主线程,那到底什么是线程?什么是进程? 进程是 CPU ...

  4. [译]Node.js - Event Loop

    介绍 在读这篇博客之前,我强列建议先阅读我的前两篇文章: Getting Started With Node.js Node.js - Modules 在这篇文章中,我们将学习 Node.js 中的事 ...

  5. Node.js学习笔记:setImmediate与process.nextTick

    通过process.nextTick注册的函数在当前这个事件循环中执行的函数执行完毕后立即执行,相当于把当前的同步代码执行完毕之后,立刻执行所有的通过process.nextTick注册的函数,如果注 ...

  6. JS event loop

    一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. Java ...

  7. 对Node.JS的事件轮询(Event Loop)的理解

    title: Node.JS的事件轮询(event loop)的理解 categories: 理解 tags: Node JS 机制 当我们知道I/O操作和创建新线程的开销是巨大的! 网站延迟的开销 ...

  8. Node.js机制及原理理解初步【转】

    一.node.js优缺点 node.js是单线程. 好处就是 1)简单 2)高性能,避免了频繁的线程切换开销 3)占用资源小,因为是单线程,在大负荷情况下,对内存占用仍然很低 3)线程安全,没有加锁. ...

  9. 关于Node.js的__dirname,__filename,process.cwd(),./文件路径的一些坑

    探索 计算机不会欺骗人,一切按照规则执行,说找不到这个文件,那肯定就是真的找不到,至于为什么找不到,那就是因为我们理解有偏差,我最初理解的'./'是当前执行js文件所在的文件夹的绝对路径,然后Node ...

随机推荐

  1. SHGetFileInfo 报错 异常 问题

    查看代码是否使用了 ::CoInitializeEx(NULL, COINIT_MULTITHREADED); 如果是,换成在每个线程调用 ::CoInitialize(NULL); 真够蛋疼的,查了 ...

  2. Netty入门(十)解码分隔符和基于长度的协议

    我们需要区分不同帧的首尾,通常需要在结尾设定特定分隔符或者在首部添加长度字段,分别称为分隔符协议和基于长度的协议,本节讲解 Netty 如何解码这些协议. 一.分隔符协议 Netty 附带的解码器可以 ...

  3. ATP学姐的模拟赛

    ATPの水题大赛 声明:不是我觉得这题水,这就是本场模拟赛的名称. T1:求所有的$n$位数中有几个数满足:每一位要么是$A$要么是$B$,并且这个$n$位数的每一位加起来是$A$或$B$的倍数. $ ...

  4. win10管理员已阻止你运行此应用”解决方法

    方法/步骤 1 按WIN+R键,打开“运行”,然后输入“gpedit.msc",就是打开组策略,这个在控制面板中也可以打开. 2 在组策略里找到“计算机配置”-“Windows设置”-“安全 ...

  5. 利用 share code 插件同步代码片段

    利用 Settings Sync可以同步 VS code 配置,但它只能同步插件,利用  Settings Sync 再配合 share code 插件可以同步自定义代码片段,可以把 VS code ...

  6. Dubbo -- 系统学习 笔记 -- 配置

    Dubbo -- 系统学习 笔记 -- 目录 配置 Xml配置 属性配置 注解配置 API配置 配置 Xml配置 配置项说明 :详细配置项,请参见:配置参考手册 API使用说明 : 如果不想使用Spr ...

  7. Python基础(10)——类进阶(静态方法、类方法、属性方法)

    以下方法为高级方法,一般来说没什么卵用 1.静态方法 @staticmethod 相当于把类内的函数从类内独立出来,只是名义上归类管,实际上不可以调用类内的变量和函数 通过@staticmethod装 ...

  8. html5获取地理位置信息API

    html5获取地理位置信息API 在HTML5中,可以看下如何使用Geolocation API来获得用户的地理位置信息,如果该浏览器支持的话,且设备具有定位功能,就能够直接使用这组API来获取当前位 ...

  9. (一)RESTful 介绍

    什么是RESTful REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”. R ...

  10. 写脚本时出现: Permission denied

    例如对文件 remove.sh sudo chmod -R 777 remove.sh