node端事件循环机制(Part1)

栏目: Node.js · 发布时间: 6年前

node 是基于谷歌v8 javascript引擎的 非阻塞事件驱动 平台, 接下来的一系列文章,我将描述什么是事件循环,它是如何工作的,它如何影响我们的应用程序。

文章指引

  • Event Loop (本文)
  • Timers、 Immediates 、 Next Ticks
  • Promises、Next-Ticks、Immediates
  • 处理 I/O
  • 最佳的事件循环练习
  • 在 Node v11中timers、microtasks发生的改变

Reactor 模式

node端事件循环机制(Part1)

nodejs 的事件驱动模型涉及 Event DemultiplexerEvent Queue 。所有I/O请求最终将生成完成/失败事件或任何其他触发器,统称为 事件 。这些事件按照以下算法处理。

  1. Event Demultiplexer 接收I/O请求,并将这些请求委托给相应的硬件。
  2. 一旦I/O请求被处理(文件中的数据、套接字中的数据可以读取等), Event Demultiplexer 将为这个特定的操作注册回掉并添加到 Event Queue
  3. Event Queue 中的事件可以被处理时,它们将按照接收到的顺序顺序执行,直到队列为空。
  4. 如果 Event Queue 中没有事件,或者 Event Demultiplexer 中没有任何挂起的请求,则程序将完成。否则,这个过程将又从第一步开始,如此循环。

注意:不要混淆 event loop 和 NodeJS Event Emitter。NodeJS Event Emitter与此机制完全不同。在后面的文章中,我将通过event loop解释NodeJS Event Emitter如何影响事件处理过程。

上面的图是对NodeJS如何工作的高级概述,并显示了一个称为Reactor 模式的主要部分。但实际却这比这复杂得多。这有多复杂呢?

Event Demultiplexer 不是一个独立的部分,它讲包括所有操作系统平台以及所有类型的I/O。

这里显示的 Event Queue 也仅仅包含一个的队列,所有类型的事件都在其中排队进入和退出队列。并不是只有I/O类型的事件会在这里排队

所以接下来让我们更加深入的理解

Event Demultiplexer

Event Demultiplexer只是一个抽象概念,它并不真实存在。在不同的操作系统中,它都有实现,有着不同的名称。

Linux 中称它 epoll 、BSD系统中称 kqueue 、Solaris中称 event ports 、Windows中的 IOCP 等。 Nodejs 使用它提供的 非阻塞、异步硬件I/O功能

File I/O的复杂性

令人困惑的是,并不是所有的I/O类型都能使用这个实现来执行,即使在同一个操作系统平台上,支持不同类型的I/O也很复杂。

例如Linux不支持文件系统访问的完全异步,为了提供完全的异步,要处理所有这些文件系统的复杂性是非常复杂的/几乎不可能的。

另外处理File I/O之外,node提供的DNS也具有这种复杂性

解决办法

因此,引入了 thread pool (线程池)来支持I/O函数,而硬件异步I/O utils(如epoll/kqueue/event ports或IOCP)无法直接处理这些函数。

现在我们知道并不是所有的I/O函数都发生在 thread pool 中。NodeJS已经尽力使用 非阻塞和异步硬件I/O 来完成大部分I/O,但是对于阻塞或难以处理的I/O类型,它使用 thread pool

将前面提到的问题聚集到一起

正如我们所看到的,很难在所有不同类型的OS平台上支持所有不同类型的I/O((file I/O, network I/O, DNS等)。一些I/O可以使用本地硬件实现来执行,同时保留完整的异步性。而一些特定的I/O应该在线程池中执行某些I/O类型,以保证完整的异步性。

开发人员对Node的一个常见误解是Node在线程池中的执行所有类型的I/O。

为了在支持跨平台I/O的同时管理整个流程,应该有一个抽象层来封装这些平台间和平台内的复杂性,并为node的上层公开一个通用API。

接下来就让我们热烈欢迎........

node端事件循环机制(Part1)

这个库提供的不仅仅是对不同I/O轮询机制的简单抽象:‘handles’ 和 ‘streams’ 为套接字和其他实体提供了高级抽象;此外,还提供了跨平台的文件I/O和线程功能。

现在我们来看看libuv是如何组成的

node端事件循环机制(Part1)

从图中我们可以看到 Event Demultiplexer 是Libuv抽象的I/O处理api集合,并公开给NodeJS的上层。libuv它不仅仅为Node提供 Event Demultiplexer 。Libuv为NodeJS提供了整个事件循环功能,包括 Event Queue

接下来让我们来看 Event Queue

Event Queue

事件队列中,所有事件都被事件循环按顺序排队和处理,直到队列为空。

在NodeJS中有多个队列,不同类型的事件在它们自己的队列中排队。

在处理一个类型的队列之后,在进入下一个队列之前,事件循环将处理两个 intermediate queues (中间队列),直到中间队列为空

那么有多少个队列呢?什么是中间队列?

有四种 主要的事件类型 ,它由本地的libuv处理。

setTimeout、setInterval
setImmediate 

有两种类型的 intermediate queues (中间队列),他们由node处理, 不属于libuv

  • Next Ticks队列: 由使用 process.nextTick 添加的事件组成
  • 其他的 Microtasks Queue (微队列): 包含了其他的微任务,例如 Promise.then()

这些不同类型的事件队列如何工作?

如下图所示,Node通过检查计时器队列中的是否有过期计时器从而开始事件循环,并在每个步骤中遍历每个队列。在处理了 close handlers queue 之后,如果任何队列中没有要处理的项,则循环将退出。可以将事件循环中每个队列的处理视为事件循环的一个阶段。

node端事件循环机制(Part1)

前面提到的中间队就是图中心得两份队列,有趣的是,一旦一个阶段完成,事件循环将检查这两个中间队列中的任何可用项。如果中间队列中有任何可用项,事件循环将立即开始处理它们,直到清空两个立即队列。直到它们为空,事件循环才会继续到下一个阶段。

Next tick queue vs Other Microtasks queue

两个中间队列的优先级不同, Next tick队列 具有比 其他微任务队列 更高的优先级。也就是说当一个阶段完成之后,会先去Next tick 队列清空任务,再去Microtasks(微任务)队列清空任务。

这些所谓的 中间队列 的约定引入了一个新问题,IO饥饿。大量使用 process.nextTick 将强制事件循环在不向前移动的情况下无限期地处理next tick队列。这将导致IO饥饿,因为如果不清空 Next tick 队列,事件循环将无法继续。

我将在后面的文章中以示例深入描述这些队列。

来一个总结,node在 Event Demultiplexer 中处理所有的异步I/O,它是 Libuv 抽象的I/O处理api集合,在I/O响应后,将相应的事件推入到相应的事件类型的队列,并在其中通过事件循环来调用回掉函数。

最后,现在您知道了什么是事件循环(即在event)、如何实现它以及Node如何处理异步I/O。现在让我们看看Libuv在NodeJS体系结构中的位置。

node端事件循环机制(Part1)

Reference


以上所述就是小编给大家介绍的《node端事件循环机制(Part1)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Practical Vim, Second Edition

Practical Vim, Second Edition

Drew Neil / The Pragmatic Bookshelf / 2015-10-31 / USD 29.00

Vim is a fast and efficient text editor that will make you a faster and more efficient developer. It’s available on almost every OS, and if you master the techniques in this book, you’ll never need an......一起来看看 《Practical Vim, Second Edition》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换