在Node.js的架构中,是否存在两个内部事件循环?
- libev/libuv
- v8 JavaScript事件循环
当进行I/O请求时,Node.js会将请求排队到libeio中,然后使用libev通过事件通知数据可用性,最后这些事件使用回调由v8事件循环处理?
基本上,libev和libeio如何与Node.js架构集成?
是否有文档可提供有关Node.js内部架构的清晰图像?
在Node.js的架构中,是否存在两个内部事件循环?
当进行I/O请求时,Node.js会将请求排队到libeio中,然后使用libev通过事件通知数据可用性,最后这些事件使用回调由v8事件循环处理?
基本上,libev和libeio如何与Node.js架构集成?
是否有文档可提供有关Node.js内部架构的清晰图像?
我个人一直在阅读node.js和v8的源代码。
当我试图理解node.js架构以编写本地模块时,我遇到了与您类似的问题。
我在此发布的是我对node.js的理解,这可能有点偏离轨道。
Libev 是事件循环,在node.js内部实际运行以执行简单的事件循环操作。它最初是为*nix系统编写的。Libev提供了一个简单而优化的事件循环,使进程可以运行。您可以在这里了解更多关于libev的信息。
LibUv 是在libeio、libev、c-ares(用于DNS)和iocp(用于Windows异步IO)之上的抽象层。LibUv执行、维护和管理事件池中的所有IO和事件。(在libeio线程池的情况下)。您应该查看Ryan Dahl的教程,这将让你更了解libUv如何工作,然后你将了解node.js如何在libuv和v8的顶部运行。
要理解JavaScript事件循环,您应该观看这些视频
要查看如何在node.js中使用libeio创建异步模块,您应该查看此示例。
基本上,在node.js内部发生的情况是v8循环运行并处理所有JavaScript部分以及C++模块[当它们在主线程中运行时(根据官方文档,node.js本身是单线程的)]。当在主线程之外时,libev和libeio在线程池中处理它,并且libev提供与主循环的交互。因此,据我理解,node.js有1个永久事件循环:那就是v8事件循环。为了处理C++异步任务,它使用线程池[通过libeio和libev]。
例如:
eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);
在所有模块中出现的通常是在线程池中调用函数Task
。当它完成时,它会在主线程中调用AfterTask
函数。而Eio_REQUEST
则是请求处理程序,可以是一个提供线程池和主线程之间通信的结构/对象。
看起来一些讨论的实体(例如:libev等)已经失去了影响力,因为它已经有一段时间了,但我认为这个问题仍然有巨大的潜力。
让我尝试用一个抽象的例子,在一个抽象的UNIX环境中,以Node的上下文,解释事件驱动模型的工作原理。
程序的角度:
上述事件机制被称为libuv AKA事件循环框架。 Node利用此库实现其事件驱动编程模型。
Node的角度:
虽然大多数功能都是以这种方式处理的,但某些文件操作的异步版本需要使用额外的线程来完成,并与libuv完美集成。虽然网络I/O操作可以等待外部事件(例如另一端点响应数据等),但文件操作需要一些来自Node.js本身的工作。例如,如果您打开一个文件并等待fd准备好数据,它是不会发生的,因为实际上没有人在读取!同时,如果您在主线程中直接从文件中读取,它可能会阻塞程序中的其他活动,并可能会出现可见问题,因为文件操作与CPU绑定的活动相比非常缓慢。因此,在保持程序的事件驱动抽象视角不变的同时,使用内部工作线程(可以通过UV_THREADPOOL_SIZE环境变量进行配置)来操作文件。
希望这有所帮助。
Node.js项目始于2009年,作为一个脱离浏览器的JavaScript环境。使用了Google的V8和Marc Lehmann的libev,Node.js将I/O模型(事件驱动)与适合这种编程风格的语言结合在一起,因为它是由浏览器塑造出来的。随着Node.js越来越流行,使其能够在Windows上运行变得重要,但libev只能在Unix上运行。与平台相关的IOCP是类似于kqueue或(e)poll的内核事件通知机制。libuv是围绕libev或IOCP的抽象,为用户提供基于libev的API。在node-v0.9.0版本的libuv中,移除了libev。
此外,还有一个图片描述了Node.js中的事件循环,由@BusyRich绘制:
更新于05/09/2017
根据Node.js事件循环文档,下图显示了事件循环的简化操作顺序。
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
注意:每个方框都将被称为事件循环的“阶段”。
阶段概述
setTimeout()
和 setInterval()
调度的回调函数。setImmediate()
。setImmediate()
回调在此处调用。socket.on('close', ...)
。在事件循环的每次运行之间,Node.js 检查是否正在等待任何异步 I/O 或计时器,并在没有任何等待时干净地关闭。
在node-v0.9.0版本的libuv中删除了libev
",但是在nodejs的changelog
中没有关于此的描述。
https://github.com/nodejs/node/blob/master/CHANGELOG.md.如果libev被移除了,那么现在nodejs是如何执行异步I/O操作的? - intekhabNodeJs的架构中有一个事件循环。
Node应用程序以单线程事件驱动模型运行。但是,Node在后台实现了一个线程池,以便可以执行工作。
Node.js将工作添加到事件队列中,然后由单个线程运行事件循环来接收它。事件循环获取事件队列中的顶部项目,执行它,然后获取下一个项目。
当执行生命周期较长或具有阻塞I/O的代码时,Node.js不会直接调用函数,而是将函数添加到事件队列中,并附加一个回调函数,在函数完成后执行该回调函数。 当Node.js事件队列中的所有事件都被执行后,Node.js应用程序终止。
当我们的应用程序功能在I/O上阻塞时,事件循环开始遇到问题。
Node.js使用事件回调来避免等待阻塞I/O。因此,执行任何执行阻塞I/O的请求都在后台的不同线程上执行。
当从事件队列中检索到一个阻塞I/O的事件时,Node.js从线程池中检索一个线程,并在那里执行该函数,而不是在主事件循环线程上执行。这样可以防止阻塞I/O阻塞事件队列中的其他事件。
libuv只提供一个事件循环,而V8仅是一个JS运行时引擎。
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应用程序就无法做其他事情。
process.nextTick
- 在下一次事件循环中调用此回调。这不是一个简单的setTimeout(fn, 0)别名,它更有效率。这指的是哪个事件循环?V8事件循环吗? - Tamil