当前位置: 首页 > 后端技术 > Node.js

节点端事件循环机制(Part1-EventLoop)

时间:2023-04-03 11:52:21 Node.js

node是一个基于谷歌v8javascript引擎的非阻塞、事件驱动的平台。在接下来的系列文章中,我将描述事件循环是什么以及它是如何工作的。它如何影响我们的应用程序。文章指导EventLoop(本文)Timers,Immediates,NextTicksPromises,Next-Ticks,ImmediatesHandlingI/O最佳事件循环实践Nodev11Reactor模式中timers和microtasks的变化nodejs的事件驱动模型涉及到EventDemultiplexer和事件队列。所有I/O请求最终都会生成完成/失败事件或任何其他触发器,统称为事件。这些事件根据以下算法进行处理。事件多路分解器接收I/O请求并将这些请求委托给适当的硬件。一旦I/O请求被处理(文件中的数据、套接字中的数据可以被读取等),EventDemultiplexer将注册这个特定的操作并将其添加到EventQueue中。当EventQueue中的事件可以被读取和处理时,它们将按照收到的顺序依次执行,直到队列为空。如果事件队列中没有事件,或者如果事件多路分解器中没有待处理的请求,程序将完成。否则,该过程将再次从第一步开始,依此类推。注意:不要将事件循环与NodeJS事件发射器混淆。NodeJSEventEmitter与这种机制完全不同。在后面的文章中,我将解释NodeJSEventEmitter是如何通过事件循环影响事件处理过程的。上图是对NodeJS工作原理的高级概述,并显示了称为Reactor模式的主要部分。但它实际上比这复杂得多。这有多复杂?EventDemultiplexer不是一个单独的部分,它涵盖了所有操作系统平台和所有类型的I/O。这里显示的EventQueue也只包含一个队列,所有类型的事件都在队列中排队进出队列。不仅仅是I/O类型的事件会在这里排队,所以让我们更深入地了解EventDemultiplexer。EventDemultiplexer只是一个抽象的概念,并不真正存在。在不同的操作系统中,它有不同名称的实现。例如在Linux中称为epoll,在BSD系统中称为kqueue,在Solaris中称为eventports,在Windows中称为IOCP等。Nodejs利用了它提供的非阻塞、异步硬件I/O能力。文件I/O的复杂性令人困惑,因为并非所有类型的I/O都可以使用此实现来执行,即使在同一操作系统平台上,支持不同类型的I/O也很复杂。例如,Linux不支持文件系统访问的完全异步,并且处理所有这些文件系统复杂性以提供完全异步是非常复杂的/几乎不可能的。除了处理FileI/O,node提供的DNS也有这种复杂的解决方案。因此引入线程池(threadpool)来支持I/O功能,而硬件异步I/Outils(如epoll/kqueue/eventports或IOCP)无法直接处理这些功能。现在我们知道并不是所有的I/O函数都发生在线程池中。NodeJS已尽力使用非阻塞和异步硬件I/O来完成大部分I/O,但对于阻塞或难以处理的I/O类型,它使用线程池。综合上述问题正如我们所见,很难在所有不同类型的操作系统平台上支持所有不同类型的I/O((文件I/O、网络I/O、DNS等)。一些I/O可以使用本地硬件实现执行,同时保持完全异步。并且一些特定的I/O应该在线程池中执行某些I/O类型,以保证完全异步。开发人员对Node感兴趣,一个常见的误解是Node在线程池中执行所有类型的I/O。为了在支持跨平台I/O的同时管理整个过程,应该有一个抽象层来封装这些跨平台和平台内的复杂性,并公开一个通用的node上层的API。那么让我们热烈欢迎...libuv是一个最初为NodeJS编写的跨平台支持库,它是围绕事件驱动的异步I/O模型设计的。该库为不同的I/O轮询机制提供的不仅仅是简单的抽象:“句柄”和“流”为套接字和其他实体提供高级抽象;此外,跨平台文件I/O和线程函数。下面我们就来看看libuv是怎么组成的。从图中我们可以看出,EventDemultiplexer是Libuv抽象出来的一组I/O处理API的集合,暴露给NodeJS上层。libuv它不仅为Node.js提供了EventDemultiplexer。libuv为NodeJS提供了整个事件循环功能,包括EventQueue接下来我们看一下EventQueueEventQueue事件队列,所有的事件都由事件循环按顺序排队处理,直到队列为空。在NodeJS中有多个队列,不同类型的事件在它们自己的队列中排队。处理完一种队列之后,在进入下一个队列之前,事件循环会处理两个中间队列(intermediatequeues),直到中间队列为空那么到底有多少个队列呢?什么是中间队列?有四种主要的事件类型,由libuv本机处理。过期计时器和间隔队列:由使用setTimeout和setInterval增加的回调组成。I/O事件队列:由完成的I/O事件组成。immediateseventqueue:由setImmediate增加的callback组成closeeventqueue:由close组成Eventhandlers由两类中间队列(intermediatequeues)组成,由node处理,不属于libuvNextTicksqueue:由events组成使用process.nextTick添加OtherMicrotasksQueue(微队列):包含其他微任务这些不同类型的事件队列,例如resolvedPromises是如何工作的?如下图所示,Node通过检查定时器队列中是否有过期的定时器来启动事件循环,每一步遍历各个队列。在处理完关闭处理程序队列后,如果任何队列中都没有要处理的项目,则循环将退出。事件循环中对每个队列的处理可以认为是事件循环的一个阶段。前面提到的中间队列是图中中心的两个队列,有趣的是,一旦一个阶段完成,事件循环刚刚开始时,事件循环会检查这两个中间队列是否有可用的项目。如果中间队列中有可用的项目,事件循环将立即开始处理它们,直到两个直接队列都被清空。事件循环不会继续到下一阶段,直到它们为空。NexttickqueuevsOtherMicrotasksqueue两个中间队列的优先级不同,Nexttickqueue比其他microtasksqueue有更高的优先级。也就是说,当一个stage完成后,会先去Nexttick队列清空任务,然后再去Microtasks(微任务)队列清空任务。这些所谓的中间队列的约定引入了一个新问题,IO饥饿。大量使用process.nextTick将强制事件循环无限期地处理下一个滴答队列而不向前移动。这将导致IO饥饿,因为如果不清空下一个滴答队列,事件循环将无法继续。我将在以后的文章中通过示例深入描述这些队列。总结一下,Node在EventDemultiplexer中处理所有的异步I/O,它是Libuv的抽象I/O处理API集。I/O响应后,将对应的事件推送到对应事件类型的队列中。并通过事件循环调用回调函数。最后,现在您知道什么是事件循环、如何实现它以及Node如何处理异步I/O。现在让我们看看Libuv在NodeJS架构中的位置。参考https://jsblog.insiderattack....(自带梯子)