【译】Node事件循环

第一次翻译文档,渣翻请见谅。
原文链接 https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
参考链接 http://www.cnblogs.com/MuYunyun/p/7287413.html
http://www.cnblogs.com/MuYunyun/p/7287413.html

什么是事件循环?

事件循环是使Node.js得以执行非阻塞I/O操作的——即使JavaScript是单线程的,通过在任何可能的时候将操作offload到系统内核。

最现代的内核是多线程的,它们能处理多个操作在后台执行。当这些操作的其中之一完成,内核告诉Node.js,使得合适的callback可以被加入到poll队列中,最终被执行。

事件循环解释

当Node.js启动,它初始化事件循环,处理提供的输入脚本(或丢入REPL),这可能导致异步API调用,调度定时器,或者调用process.nextTick(),然后开始处理事件循环。

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

每个阶段有一个callback的FIFO队列等待执行。一般来说,当事件循环进入到一个给定的阶段,它会执行这个阶段的所有具体的操作,然后执行这个阶段的队列中的callback,直到队列好近或callback 的最大数量被执行。之后事件循环会移动到下一个阶段。

阶段概览

  • timers: 计时器,这个阶段执行通过setTimeout()和setInterval()注册的回调函数。
  • pending callbacks: 执行被推迟到下个循环迭代的I/O回调函数,大部分回调将在这里被处理
  • idle, prepare: 只在内部使用
  • poll: 轮询,对接着要处理的I/O事件进行新的轮询,执行与I/O相关的回调函数(几乎所有,除了close callbacks,这个通过即时起注册,以及setImmediate())
  • check: setImmediate()回调函数在这里被调用
  • close callbacks: 一些关闭回调函数,如socket.on('close', ...), 处理所有‘结束’事件的回调。
    在每轮事件循环期间,Node.js检查是否在等待任何异步I/O或计时器,如果没有,就完整关闭。

阶段细节

timers

一个定时器指定阈值,一个提供的回调函数可能在这个threshold阈值之后,而不是我们想要它被执行的exact实际的时间被执行。定时器的回调函数会在它们被指派在指定长度的时间之后尽早执行;然而,操作系统调度或其他回调函数的运行可能会推迟它们。
注意:理论上,exact轮询阶段控制定时器何时被执行。
例如,你设置一个timeout在100ms的阈值吼执行,然后你的搅拌开始异步读取一个文件,读取文件花费95ms:

const fs = require('fs');

function someAsyncOperation(callback) {
  // Assume this takes 95ms to complete
  fs.readFile('/path/to/file', callback);
}

const timeoutScheduled = Date.now();

setTimeout(() => {
  const delay = Date.now() - timeoutScheduled;

  console.log(`${delay}ms have passed since I was scheduled`);
}, 100);


// do someAsyncOperation which takes 95 ms to complete
someAsyncOperation(() => {
  const startCallback = Date.now();

  // do something that will take 10ms...
  while (Date.now() - startCallback < 10) {
    // do nothing
  }
});

当事件循环进入poll轮询阶段,它有一个空的队列(fs.readFile()尚未完成),因此它会等待剩余的毫秒数直到达到最快定时器的阈值。当等待了95ms,fs.readFile()完成读取文件,它的花费10ms来完成的回调函数被加入到poll队列并被执行。当回调函数完成,队列中没有其他的回调函数了,于是事件循环会看最快定时器的阈值已经被达到,然后wrap back回到timers定时器阶段来执行定时器的回调函数。在这个例子中,你会看到定时器被设置和它的回调函数被执行的总延迟为105ms。

注意: 为了避免poll轮询阶段耗尽事件循环,libuv(实现Node.js事件循环和平台的所有异步行为的C库)也有一个hard最大值(系统依赖)来阻止对更多事件轮询。

pending callbacks

这个阶段为一些系统操作,如TCP错误类型,执行回调函数。例如,如果一个TCP套接字在尝试连接时接收到了ECONNREFUSED,一些系统想等待来报告这个错误。这回被放入pending callbacks的队列中来执行。

poll

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

  1. 计算它该阻塞多久和轮询I/O, 然后
  2. 处理poll队列中的事件。
    当事件循环进入poll阶段,且没有定时器被设置,两件事之一会发生:
  • 如果poll阶段的队列不为空,则事件循环会遍历回调函数的队列,同步执行它们,直到队列为空或达到系统依赖的hard limit。
  • 如果poll阶段的队列为空,则以下两件事之一会发生:
    • 如果脚本已经被setImmediate()设置,事件循环会终止poll阶段并继续到check阶段来执行那些被设置的脚本。
    • 如果脚本尚未被setImmediate()设置,事件循环会等待回调函数被加入到队列中,然后立刻执行它们

一旦poll队列为空,事件循环会检查timers定时器,它们的时间阈值被达到。如果一个或多个定时器就绪,事件循环会回到timers阶段来执行那些定时器的回调函数。

check

这个阶段允许我们在poll阶段完成后立即执行回调函数。如果poll阶段变为闲置,且脚本被setImmediate()设置,事件循环会来到check阶段而不是等待。

setImmediate()实际是一个特殊的定时器,运行在事件循环的分开的阶段。它使用一个libuvAPI,这个API设置回调函数在poll阶段完成后来执行。

一般来说,在代码被执行时,事件循环最终会进入poll阶段,在这里它会等待一个到来的连接,请求,等等。然而,如果一个回调函数已经被setImmediate()设置且poll阶段变为闲置,它会终止并进入到check阶段而不是等待poll轮询事件。

close callbacks

如果一个socket或handle被突然关闭(e.g. socket.destroy()),这个'close'事件会在这个阶段被射出。否则它会通过process.nextTick()被射出。

setImmediate() VS setTimeout()
两者类似,但是根据被调用的时间会发生不同的行为。

  • setImmediate()被设计为一旦现在的poll阶段完成就执行。
  • setTimeout()设置一个脚本在一个最小毫米单位阈值过去之后执行。

定时器被执行的顺序会根据它们被调用时处在的上下文而变化。如果都在主模块中被调用,则timing会被进程的性能限制(进程的性能可能会被机器上运行的其他应用程序影响)。

例如,如果我们运行以下的不处于一个I/O cycle(也就是主模块)内部的脚本,这两个定时器被执行的顺序是不一定的,因为受到了进程性能的限制:

// timeout_vs_immediate.js
setTimeout(() => {
  console.log('timeout');
}, 0);

setImmediate(() => {
  console.log('immediate');
});
$ node timeout_vs_immediate.js
timeout
immediate

$ node timeout_vs_immediate.js
immediate
timeout

然而,如果你把两个调用移动到一个I/O cycle里面,immediate 回调函数总是先执行:

// timeout_vs_immediate.js
const fs = require('fs');

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

$ node timeout_vs_immediate.js
immediate
timeout

使用setImmediate()而不是setTimeout()的主要优点是,当处在I/O cycle之内时,前者总是在任何其他定时器之前执行,不管当前有多少个定时器。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269