Nodejs 事件循环

IT技术 javascript node.js event-loop libev
2021-02-05 09:02:17

nodejs架构内部是否有两个事件循环?

  • libev/libuv
  • v8 javascript 事件循环

在 I/O 请求中,节点是否将请求排队发送到 libeio,libeio 又通过使用 libev 的事件通知数据的可用性,最后这些事件由 v8 事件循环使用回调处理?

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

是否有任何文档可以清楚地了解 nodejs 内部架构?

6个回答

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

当我试图理解 node.js 架构以编写原生module时,我遇到了和你类似的问题。

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

  1. libev是一个事件循环,它实际上在 node.js 内部运行,以执行简单的事件循环操作。它最初是为 *nix 系统编写的。Libev 为进程运行提供了一个简单但经过优化的事件循环。您可以在此处阅读有关 libev 的更多信息

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

  3. LibUv是位于 libeio、libev、c-ares(用于 DNS)和 iocp(用于 Windows 异步 io)之上的抽象层。LibUv 执行、维护和管理事件池中的所有 io 和事件。(在 libeio 线程池的情况下)。您应该查看Ryan Dahl关于 libUv的教程这将使您对 libUv 本身的工作方式更有意义,然后您将了解 node.js 如何在 libuv 和 v8 之上工作。

要仅了解 javascript 事件循环,您应该考虑观看这些视频

要了解如何将 libeio 与 node.js 结合使用以创建异步module,您应该查看此示例

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

例如:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

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

有没有办法“看到”这个事件?我希望能够看到堆栈上的调用顺序并看到新函数被推送到那里以更好地了解正在发生的事情......是否有一些变量可以告诉您什么被推送到事件队列?
2021-03-12 09:02:17
@Raynos libuv 旨在确保其 x-platfousing 多个库。对 ?因此使用 libuv 是个好主意
2021-03-16 09:02:17
@Abhishek From Doc process.nextTick- 在围绕事件循环的下一个循环中调用此回调。这不是 setTimeout(fn, 0) 的简单别名,它的效率更高。这是指哪个事件循环?V8 事件循环?
2021-03-20 09:02:17
请注意, libuv不再在 libev 之上实现
2021-03-27 09:02:17
依靠 libuv 在内部使用 libev 的事实是使您的代码不跨平台的好方法。你应该只关心 libuv 的公共接口。
2021-04-10 09:02:17

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

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

节目观点:

  • 脚本引擎开始执行脚本。
  • 任何时候遇到 CPU 绑定操作,它都会完整地内联(真机)执行。
  • 任何时候遇到 I/O 绑定操作,请求及其完成处理程序都会注册到“事件机制”(虚拟机)
  • 以上述相同的方式重复操作,直到脚本结束。CPU 绑定操作 - 执行内联、I/O 绑定操作,向上述机器发出请求。
  • 当 I/O 完成时,监听器被回调。

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

节点视角:

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

虽然大多数功能都以这种方式提供,但一些(异步版本)文件操作是在附加线程的帮助下执行的,这些线程很好地集成到 libuv 中。虽然网络 I/O 操作可以等待外部事件,例如另一个端点响应数据等,但文件操作需要节点本身的一些工作。例如,如果您打开一个文件并等待 fd 准备好数据,则不会发生,因为实际上没有人在阅读!同时,如果您在主线程中内联读取文件,它可能会阻塞程序中的其他活动,并可能产生可见的问题,因为与 cpu 绑定活动相比,文件操作非常慢。因此使用内部工作线程(可通过 UV_THREADPOOL_SIZE 环境变量配置)对文件进行操作,

希望这可以帮助。

你是怎么知道这些事情的,能指点一下来源吗?
2021-03-26 09:02:17

libuv 简介

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

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


更新 05/09/2017

根据此文档Node.js 事件循环

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

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

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

阶段概述

  • timers:此阶段执行由setTimeout()调度的回调setInterval()
  • I/O 回调:执行几乎所有回调,除了关闭回调、计时器调度的回调setImmediate().
  • 空闲,准备:仅在内部使用。
  • poll : 检索新的 I/O 事件;节点会在适当的时候阻塞在这里。
  • check :setImmediate()回调在这里被调用。
  • 关闭回调:例如socket.on('close', ...)

在事件循环的每次运行之间,Node.js 检查它是否正在等待任何异步 I/O 或计时器,如果没有,则干净地关闭。

您已经引用了“ In the node-v0.9.0 version of libuv libev was removed”,但是在 nodejs 中没有关于它的描述changeloggithub.com/nodejs/node/blob/master/CHANGELOG.md如果 libev 被删除,那么现在如何在 nodejs 中执行异步 I/O?
2021-03-13 09:02:17
如果 Node.Js 应用程序收到请求。并且在这个请求中需要执行像 setTimeout(() => { console.log('timeout'); }, 10); 这样的代码。setImmediate(()=> { console.log('timeout'); }); console.log("Main") 然后 nodeJs 将如何将任务移动到计时器,检查,轮询阶段并热执行
2021-03-22 09:02:17
@intekhab 我认为 libuv 正在实现与 I/O 和轮询相关的所有功能。在这里检查这个文档:docs.libuv.org/en/v1.x/loop.html
2021-03-28 09:02:17
@intekhab,根据这个链接,我认为基于 libeio 的 libuv 可以用作 node.js 中的事件循环。
2021-04-09 09:02:17

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

Node.js 事件循环模型

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

Node.js 将工作添加到事件队列中,然后有一个运行事件循环的线程来接它。事件循环抓取事件队列中的顶部项目,执行它,然后抓取下一个项目。

当执行生命周期较长或具有阻塞 I/O 的代码时,它不会直接调用函数,而是将函数与将在函数完成后执行的回调一起添加到事件队列中。当 Node.js 事件队列上的所有事件都执行完毕后,Node.js 应用程序将终止。

当我们的应用程序函数阻塞 I/O 时,事件循环开始遇到问题。

Node.js 使用事件回调来避免等待阻塞 I/O。因此,任何执行阻塞 I/O 的请求都在后台的不同线程上执行。

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

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