美文网首页我爱编程
【译】Node事件循环

【译】Node事件循环

作者: Observer_____ | 来源:发表于2018-05-17 23:24 被阅读0次

第一次翻译文档,渣翻请见谅。
原文链接 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之内时,前者总是在任何其他定时器之前执行,不管当前有多少个定时器。

相关文章

  • 【译】Node事件循环

    第一次翻译文档,渣翻请见谅。原文链接 https://nodejs.org/en/docs/guides/even...

  • node 事件

    1、事件 1.1普通事件的使用 1.2、Node.js 的事件循环机制解析 1)Node 由事件循环开始,到事件循...

  • node 事件循环

    概念 -单线程、单进程,结合V8的异步回调接口,处理大量并发-API支持回调函数-事件机制采用设计模式中观察者模式...

  • Node事件循环

    Node.js 事件循环机制 Node.js 采用事件驱动和异步 I/O 的方式,实现了一个单线程、高并发的 Ja...

  • node事件循环

    事件循环 事件循环是一个典型的生产者/消费者模式,网络请求,异步IO源源不断的产生提供不同类型的事件到观察者哪里,...

  • node事件循环

    浏览器事件循环见:https://www.jianshu.com/p/64bbefbe5ae5[https://w...

  • Node事件循环

    Node架构图 事件循环核心 核心模块就是LIBUV 在linux上,libuv是对epoll的封装; 在wind...

  • Node事件循环

    官网事件循环:https://nodejs.org/zh-cn/docs/guides/event-loop-ti...

  • 学习 nodejs I /O 交互

    1 事件循环 Node的执行模型实际上是事件循环。在进程启动时,Node会创建一个无限循环,每一次执行循环体的过程...

  • node.js的事件循环

    在node中,事件循环表现出的状态与浏览器中大致相同。不同的是node中有一套自己的模型。node中事件循环的实现...

网友评论

    本文标题:【译】Node事件循环

    本文链接:https://www.haomeiwen.com/subject/eosidftx.html