Node event loop 扒皮

时间循环的流程如下

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

每个phase都维持着一个FIFO的回调函数队列。

Phases

  • timers: this phase executes callbacks scheduled by setTimeout() and setInterval().
  • I/O callbacks: executes almost all callbacks with the exception of close callbacks, the ones scheduled by timers, and setImmediate().
  • idle, prepare: only used internally.
  • poll: retrieve new I/O events; node will block here when appropriate.
  • check: setImmediate() callbacks are invoked here.
  • close callbacks: e.g. socket.on('close', ...).

timer

Technically, the poll phase controls when timers are executed.

I/0 callbacks

执行一些系统操作的回调函数,如TCP错误类型。

eg:当尝试连接时,如果一个TCP套接字收到了ECONNREFUSED, 一些linux系统可能希望等待错误报告。这将会在本阶段内入队等待执行。

poll

本阶段有两个主要功能:

  1. 执行队列里已到期的timer,然后
  2. 处理poll队列里的事件

如果没有timer调度,将会发生以下两种之一:

  • 如果poll队列不为空,则继续同步迭代器队列内的回调函数直到exhausted或是达到系统硬性限制。
  • 如果poll队列为空, 将会发生以下两种之一。
    • 如果脚本被setImmediate()调度,时间循环将会结束poll阶段并进入check阶段执行被调度的脚本。
    • 如果脚本不是被setImmediate()调度,事件循环将会等待回调函数加入队列,然后即时之行之。

一旦poll队列为空,时间循环会检查到达threshold的timers。如果一个或多个timer ready了,时间循环将会包装会timer阶段执行timer的回调函数。

check

本阶段允许在poll阶段结束后立即执行callbacks。如果poll阶段转为idle并且被setImmediate()入队,时间循环将直接进入check阶段而不是等待poll事件。

setImmediate()实际上是在事件循环中一个单独的阶段运行的一个特殊的timer。使用一个libuv的api来调度在poll阶段完成之后的callbacks的执行

通常来说,在代码执行过程中,事件循环将最终hit到poll阶段(poll阶段也会等待for incoming connection, request,etc)。但如果一个callback被setImmediate()调度并且poll阶段变为idle,it(poll)将直接结束并进入check阶段而非等待poll事件。

close callbacks

如果一个socket或是handle被突然关闭(e.g. socket.destroy()),close事件将会被emit到本阶段。其他情况将会被emit到process.nextTick()


setImmediate() vs setTimeout()

这两者很相似,但是行为表现不同 depend on 何时调用。

  • setImmediate()被设计为在当前poll阶段完成时执行
  • setTimeout()调度一段脚本在达到minimum的threshold时执行

setImmediate()oversetTimeout()的最主要优点在于: setImmediate()的执行顺序比任何在I/O阶段调度的timer高。


process.nextTick()

任何时候你都可以在一个给定的阶段里调用process.nextTick(),所有传入process.nextTick()的callbacks都会在事件循环continue前resolved.这可以创建一些很不好的解决方案,因为你可以利用递归process.nextTick()手动starve将你的I/O,这种做法将防止事件循环进入poll阶段。

process.nextTick() vs setImmediate()

  • process.nextTick()在本阶段立即解除
  • setImmediate()事件循环中的接下来的iteration或是tick中解除

Why use process.nextTick()

有两个主要原因:

  1. 允许使用者处理错误,清初任何不必要的资源,或是可能在下次事件循环时尝试再次请求
  2. 在下次事件循环前,有时候有必要在调用栈释放后,允许一个callback执行

ps:

  1. 如果可能的话,调用setTimeout时,尽量使用相同的超时值
  2. 尽量用process.nextTick 来代替 setTimeout(fn ,0)
最近的文章

React 的快与慢

在 React 出现之前,web 的性能瓶颈是 js 和 DOM 交互这块,当时很多优化大多集中在如何减少不必要的 DOM 操作,这些优化都是点状的优化,缺乏一个大而全的优化体系,直到 React 出现。众所周知 React 使用 Virtual DOM 来描述 UI 界面,开发者只需要操作改变 Virtual DOM。React 通过对比前后两个 Virtual DOM 的差异(diff 的过程),精准的拿到要发生变化的 UI 描述,这样就避免了不必要的 DOM 操作,减少了页面重绘的次...…

继续阅读
更早的文章

Mybatis explore for taste

数据库连接池使用的是Druid。Mybatis与spring-boot的集成与mybatis与Spring的集成并没有什么不同的地方。可以看这里:demo多了一个@ImportResource("mybatis.xml")注解。如果要引用多个配置,那么就需要自定义配置的引入方式。不然每一个都要去@ImportResource…

java继续阅读