Nodejs事件循环

时间:2012-05-21 06:46:33

标签: javascript node.js event-loop libev

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

  • libev / libuv
  • v8 javascript event loop

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

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

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

8 个答案:

答案 0 :(得分:165)

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

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

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

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

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

  3. LibUv是libeio,libev,c-ares(针对DNS)和iocp(针对windows asynchronous-io)的顶层的抽象层。 LibUv执行,维护和管理事件池中的所有io和事件。 (在libeio线程池的情况下)。你应该在libUv上查看Ryan Dahl's tutorial。这将开始让你更了解libUv如何工作,然后你将了解node.js如何在libuv和v8的顶部工作。

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

    要了解如何将libeio与node.js一起使用以创建异步模块,您应该看到this example

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

    例如:

    eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);
    

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

答案 1 :(得分:19)

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

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

计划的观点:

  • 脚本引擎开始执行脚本。
  • 每当遇到CPU绑定操作时,都会以内联方式(真实机器)执行它的完整性。
  • 每当遇到I / O绑定操作时,请求及其完成处理程序都会在“事件机器”中注册。 (虚拟机)
  • 以相同方式重复操作,直到脚本结束。 CPU绑定操作 - 执行内联,I / O绑定操作,如上所述向机器请求。
  • 当I / O完成时,将回叫侦听器。

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

节点的观点:

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

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

希望这有帮助。

答案 2 :(得分:17)

An Introduction to libuv

  

node.js项目于2009年开始,作为与浏览器分离的JavaScript环境。使用谷歌的V8和Marc Lehmann的libev,node.js将I / O模型 - 偶数 - 与一种非常适合编程风格的语言相结合;由于浏览器的形成方式。随着node.js越来越流行,让它在Windows上运行很重要,但libev只能在Unix上运行。 Windows等效的内核事件通知机制(如kqueue或(e)轮询)是IOCP。 libuv是一个围绕libev或IOCP的抽象,取决于平台,为用户提供基于libev的API。在libuv libev was removed的node-v0.9.0版本中。

还有一张用@ BusyRich

描述Node.js中的事件循环的图片

更新05/09/2017

根据此文档Node.js event loop

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

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

注意:每个方框都将被称为&#34;阶段&#34;事件循环。

阶段概述

  • 计时器:此阶段执行setTimeout()setInterval()计划的回调。
  • I / O回调:执行几乎所有回调,但
  • 除外
  • 关闭回调,由计时器和setImmediate()安排的回调。 闲置,准备:仅在内部使用。
  • 民意调查:检索新的I / O事件;节点将在适当的时候阻止。
  • 检查:此处调用setImmediate()回调。
  • 关闭回调:例如socket.on('close', ...)

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

答案 3 :(得分:12)

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

Node.js事件循环模型

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

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

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

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

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

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

答案 4 :(得分:8)

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

答案 5 :(得分:0)

作为一个JavaScript初学者,我也有同样的疑问,NodeJS是否包含2个事件循环?经过长期的研究和与V8贡献者的讨论,我得到了以下概念。

  • 事件循环是JavaScript编程模型的基本抽象概念。因此,V8引擎为事件循环提供了默认实现, 嵌入器(浏览器,节点)可以替换或扩展 。你们可以找到事件循环here的V8默认实现
  • 在NodeJS中,仅存在一个事件循环 ,由节点运行时提供。 V8默认事件循环实现已被NodeJS事件循环实现取代

答案 6 :(得分:0)

pbkdf2函数具有JavaScript实现,但实际上将所有工作委托给C ++端。

env->SetMethod(target, "pbkdf2", PBKDF2);
  env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
  env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
  env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
  NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
  NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
  NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
  NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
  NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
  env->SetMethod(target, "randomBytes", RandomBytes);
  env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
  env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
  env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
  env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
  env->SetMethodNoSideEffect(target, "getCurves", GetCurves);
  env->SetMethod(target, "publicEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_encrypt_init,
                                         EVP_PKEY_encrypt>);
  env->SetMethod(target, "privateDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_decrypt_init,
                                         EVP_PKEY_decrypt>);
  env->SetMethod(target, "privateEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_sign_init,
                                         EVP_PKEY_sign>);
  env->SetMethod(target, "publicDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_verify_recover_init,
                                         EVP_PKEY_verify_recover>);

资源:https://github.com/nodejs/node/blob/master/src/node_crypto.cc

Libuv模块还有另一个职责,与标准库中的某些非常特殊的功能有关。

对于某些标准库函数调用,Node C ++端和Libuv决定完全在事件循环之外进行昂贵的计算。

取而代之的是,它们使用了一个称为线程池的东西,该线程池是由四个线程组成的一系列线程,可用于运行计算量大的任务,例如pbkdf2函数。

默认情况下,Libuv在此线程池中创建4个线程。

除了事件循环中使用的线程外,还有其他四个线程可用于卸载需要在应用程序内部进行的昂贵的计算。

Node标准库中包含的许多功能会自动使用此线程池。 pbkdf2函数就是其中之一。

此线程池的存在非常重要。

所以Node并不是真正的单线程,因为Node还使用其他线程来执行一些计算量大的任务。

如果事件池负责执行计算量大的任务,那么我们的Node应用程序将无能为力。

我们的CPU在线程中一条一条地运行所有指令。

通过使用线程池,我们可以在进行计算时在事件循环内执行其他操作。

答案 7 :(得分:0)

简单来说,Node 事件循环是架构级别的循环或循环,它帮助 Javascript 代码处理异步代码。

事件循环内部有不同的循环/循环,用于处理适当的作业,例如 setTimeouts、setimmediate、文件系统、网络请求、promise 和其他东西。