Nodejs事件循环

在nodejs体系结构中是否有内部的两个事件循环?

  • libev / libuv
  • v8 JavaScript事件循环

在I / O请求上,节点将请求排队到libeio,反过来使用libev通过事件通知数据的可用性,最后这些事件由使用callback的v8事件循环处理?

基本上,libev和libeio如何集成在nodejs架构中?

是否有任何文档可用于清楚了解nodejs内部体系结构?

我一直亲自阅读node.js&v8的源代码。

当我尝试了解node.js体系结构以编写本机模块时,我遇到了类似的问题。

我在这里发表的是我对node.js的理解,这也可能有点偏离轨道。

  1. Libev是在node.js中实际运行的事件循环,用于执行简单的事件循环操作。 它最初是为* nix系统编写的。 Libev为进程运行提供了一个简单但优化的事件循环。 你可以在这里阅读关于libev的更多信息。

  2. LibEio是一个asynchronous执行input输出的库。 它处理文件描述符,数据处理程序,套接字等。您可以在这里阅读更多关于它的信息 。

  3. LibUv是libeio,libev,c-ares(用于DNS)和iocp(用于windows asyncronous-io)的顶层的抽象层。 LibUv执行,维护和pipe理事件池中的所有io和事件。 (在libeio线程池的情况下)。 你应该检查出Ryan Dahl的 libUv 教程 。 对于libUv是如何工作的,你会明白node.js是如何工作在libuv和v8上的。

要了解JavaScript事件循环,您应该考虑观看这些video

  • JS-会议
  • JSConf2011(有非常刺激性的sfx)
  • 了解事件驱动的编程
  • 了解node.js事件循环

要看看libeio如何与node.js一起使用来创buildasynchronous模块,您应该看到这个例子 。

基本上,在node.js里面发生了什么事情,v8循环运行并处理所有的javascript部分以及C ++模块[当他们在主线程中运行时(根据官方文档node.js本身是单线程的]]。 当在主线程之外时,libev和libeio在线程池中处理它,libev提供与主循环的交互。 所以从我的理解,node.js有1永久事件循环:这是V8事件循环。 为了处理C ++asynchronous任务,它使用了一个线程池(通过libeio&libev)。

例如:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST); 

出现在所有模块中的通常是在线程池中调用Task函数。 完成后,它会在主线程中调用AfterTask函数。 而Eio_REQUEST是请求处理程序,它可以是一个结构/对象,其动机是提供Eio_REQUEST之间的通信。

介绍libuv

node.js项目始于2009年,是一个与浏览器分离的JavaScript环境。 通过使用Google的V8和Marc Lehmann的libev ,node.js将I / O模型与一种非常适合编程风格的语言结合在了一起, 由于它被浏览器所塑造的方式。 随着node.js越来越stream行,重要的是使它在Windows上工作,但libev只在Unix上运行。 内核事件通知机制(如kqueue或(e)poll)的Windows等价物是IOCP。 libuv是libev或IOCP的一个抽象,取决于平台,为用户提供一个基于libev的API。 在libuv libev的node-v0.9.0版本中被删除 。

还有一张图片,描述了@ BusyRich在Node.js中的事件循环


更新05/09/2017

根据这个文档Node.js事件循环 ,

下图显示了事件循环的操作顺序的简要概述。

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

注意:每个方框将被称为事件循环的“阶段”。

阶段概述

  • 定时器 :此阶段执行由setTimeout()setInterval()调度的callback。
  • I / Ocallback :除了执行几乎所有的callback外
  • closurescallback ,定时器计划的callback ,和setImmediate() 。 闲置,准备:只在内部使用。
  • 民意调查 :检索新的I / O事件; 节点将在适当的时候阻塞。
  • 检查setImmediate()callback在这里被调用。
  • closurescallback :例如socket.on('close', ...)

在事件循环的每次运行之间,Node.js检查是否正在等待任何asynchronousI / O或定时器,如果没有任何asynchronousI / O或定时器,则清除closures。

NodeJs架构中有一个事件循环。

Node.js事件循环模型

节点应用程序以单线程事件驱动的模式运行。 但是,Node在后台实现一个线程池,以便可以执行工作。

Node.js将工作添加到事件队列中,然后运行事件循环的单个线程将其选中。 事件循环捕获事件队列中的顶层项目,执行它,然后抓取下一个项目。

当执行更长的代码或阻塞I / O时,不是直接调用函数,而是将函数与函数完成后执行的callback函数一起添加到事件队列中。 当Node.js事件队列上的所有事件都已经执行时,Node.js应用程序终止。

当我们的应用程序function阻塞I / O时,事件循环开始引起问题。

Node.js使用事件callback来避免不得不等待阻塞I / O。 因此,执行阻塞I / O的任何请求都在后台的另一个线程中执行。

当从事件队列中检索到阻塞I / O的事件时,Node.js从线程池中检索一个线程,并在那里执行函数而不是在主事件循环线程上。 这可以防止阻塞I / O阻止事件队列中的其余事件。

看起来像讨论的一些实体(例如:libev等)已经失去了相关性,因为它已经有一段时间了,但我认为这个问题仍然有很大的潜力。

让我试着在一个抽象的UNIX环境下,在Node的上下文中,通过一个抽象的例子来解释事件驱动模型的工作。

计划的angular度:

  • 脚本引擎开始执行脚本。
  • 任何时候遇到一个CPU绑定操作,它都会以内联(真实机器)的方式执行。
  • 每次遇到I / O绑定操作时,请求及其完成处理程序都会向“事件机器”(虚拟机)注册,
  • 重复上述操作,直到脚本结束。 CPU绑定操作 – 在线执行,I / O绑定操作,请求如上所述的机器。
  • 当I / O完成时,监听器被回叫。

上面的事件机制被称为libuv AKA事件循环框架。 节点利用这个库来实现其事件驱动的编程模型。

节点的angular度来看:

  • 有一个线程来承载运行时。
  • 拿起用户脚本。
  • 将其编译为本机[杠杆v8]
  • 加载二进制文件,并跳转到入口点。
  • 编译后的代码使用编程原语在线执行CPU绑定的活动。
  • 许多I / O和定时器相关的代码都有本地包装。 例如,networkingI / O。
  • 因此,I / O调用从脚本路由到C ++桥,I / O句柄和完成句柄作为parameter passing。
  • 本地代码执行libuv循环。 它获取循环,将表示I / O的低级别事件和本地callback包装排入libuv循环结构。
  • 本地代码返回到脚本 – 目前没有I / O发生!
  • 上面的项目被重复了很多次,直到所有的非I / O代码都被执行,并且所有的I / O代码都被注册了libuv。
  • 最后,当系统中没有任何内容执行时,节点将控制权交给libuv
  • libuv进入行动,它将获取所有注册的事件,查询操作系统以获得其可操作性。
  • 那些在非阻塞模式下已经准备好I / O的应用程序被拾取,执行I / O,并发出它们的callback。 一个接一个地。
  • 那些还没有准备好的(例如一个套接字读取,另一个端点没有写任何东西)将继续被操作系统探测,直到它们可用。
  • 循环内部保持一个不断增加的计时器。 当应用程序请求延迟callback(如setTimeout)时,将使用此内部计时器值来计算触发callback的正确时间。

虽然大多数function都以这种方式迎合,但文件操作的一些(asynchronous版本)是在其他线程的帮助下完成的,这些线程很好地集成到了libuv中。 虽然networkingI / O操作可以等待外部事件的预期,例如其他端点响应数据等,但文件操作需要节点本身的一些工作。 例如,如果您打开一个文件并等待fd准备好数据,则不会发生,因为没有人正在阅读! 同时,如果你在主线程中内联读取文件,它可能会阻塞程序中的其他活动,并且会产生明显的问题,因为与cpu绑定活动相比文件操作非常慢。 所以内部工作者线程(可以通过UV_THREADPOOL_SIZE环境variables来configuration)用于对文件进行操作,而从程序的angular度来看,事件驱动的抽象工作则完好无损。

希望这可以帮助。

只有一个由libuv提供的事件循环,V8只是一个JS运行时引擎。