Nodejs事件循环
在nodejs体系结构中是否有内部的两个事件循环?
- libev / libuv
- v8 JavaScript事件循环
在I / O请求上,节点将请求排队到libeio,反过来使用libev通过事件通知数据的可用性,最后这些事件由使用callback的v8事件循环处理?
基本上,libev和libeio如何集成在nodejs架构中?
是否有任何文档可用于清楚了解nodejs内部体系结构?
我一直亲自阅读node.js&v8的源代码。
当我尝试了解node.js体系结构以编写本机模块时,我遇到了类似的问题。
我在这里发表的是我对node.js的理解,这也可能有点偏离轨道。
-
Libev是在node.js中实际运行的事件循环,用于执行简单的事件循环操作。 它最初是为* nix系统编写的。 Libev为进程运行提供了一个简单但优化的事件循环。 你可以在这里阅读关于libev的更多信息。
-
LibEio是一个asynchronous执行input输出的库。 它处理文件描述符,数据处理程序,套接字等。您可以在这里阅读更多关于它的信息 。
-
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运行时引擎。