Javascript Nodejs 事件循环

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10680601/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 02:29:16  来源:igfitidea点击:

Nodejs Event Loop

javascriptnode.jsevent-looplibev

提问by Tamil

Are there internally two event loops in nodejs architecture?

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

  • libev/libuv
  • v8 javascript event loop
  • libev/libuv
  • v8 javascript 事件循环

On an I/O request does node queue the request to libeio which in turn notifies the availability of data via events using libev and finally those events are handled by v8 event loop using callbacks?

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

Basically, How are libev and libeio integrated in nodejs architecture?

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

Are there any documentation available to give a clear picture of nodejs internal architecture?

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

回答by ShrekOverflow

I have been personally reading the source code of node.js & v8.

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

I went into a similar problem like you when I tried to understand node.js architecture in order to write native modules.

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

What I am posting here is my understanding of node.js and this might be a bit off track as well.

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

  1. Libevis the event loop which actually runs internally in node.js to perform simple event loop operations. It's written originally for *nix systems. Libev provides a simple yet optimized event loop for the process to run on. You can read more about libev here.

  2. LibEiois a library to perform input output asynchronously. It handles file descriptors, data handlers, sockets etc. You can read more about it here here.

  3. LibUvis an abstraction layer on the top of libeio , libev, c-ares ( for DNS ) and iocp (for windows asynchronous-io). LibUv performs, maintains and manages all the io and events in the event pool. ( in case of libeio threadpool ). You should check out Ryan Dahl's tutorialon libUv. That will start making more sense to you about how libUv works itself and then you will understand how node.js works on the top of libuv and v8.

  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 之上工作。

To understand just the javascript event loop you should consider watching these videos

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

To see how libeio is used with node.js in order to create async modules you should see this example.

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

Basically what happens inside the node.js is that v8 loop runs and handles all javascript parts as well as C++ modules [ when they are running in a main thread ( as per official documentation node.js itself is single threaded) ]. When outside of the main thread, libev and libeio handle it in the thread pool and libev provide the interaction with the main loop. So from my understanding, node.js has 1 permanent event loop: that's the v8 event loop. To handle C++ async tasks it's using a threadpool [via libeio & libev ].

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

For example:

例如:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

Which appears in all modules is usually calling the function Taskin the threadpool. When it's complete, it calls the AfterTaskfunction in the main thread. Whereas Eio_REQUESTis the request handler which can be a structure / object whose motive is to provide communication between the threadpool and main thread.

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

回答by Gireesh Punathil

Looks like some of the entities discussed (eg: libev etc.) have had lost relevance, due to the fact that it has been a while, but I think the question still has great potential.

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

Let me try to explain the working of event driven model with the help of an abstract example, in an abstract UNIX environment, in Node's context, as of today.

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

Program's perspective:

节目观点:

  • Script engine starts execution of the script.
  • Any time a CPU bound operation is encountered, it is executed inline (real machine), in its completeness.
  • Any time an I/O bound operation is encountered, the request, and its completion handler are registered with an 'event machinery' (virtual machine)
  • Repeat the operations in the same manner above until the script ends. CPU bound operation - execute in-line, I/O bound ones, request to the machinery as above.
  • When I/O completes, the listeners are called back.
  • 脚本引擎开始执行脚本。
  • 任何时候遇到 CPU 绑定操作,它都会完整地内联(真机)执行。
  • 任何时候遇到 I/O 绑定操作,请求及其完成处理程序都会注册到“事件机制”(虚拟机)
  • 以上述相同的方式重复操作,直到脚本结束。CPU 绑定操作 - 执行内联、I/O 绑定操作,向上述机器发出请求。
  • 当 I/O 完成时,监听器被回调。

The event machinery above is called libuv AKA event loop framework. Node leverages this library to implement its event driven programming model.

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

Node's perspective:

节点视角:

  • Have one thread to host the runtime.
  • Pick up the user script.
  • Compile it into native [ leverage v8 ]
  • Load the binary, and jump into the entry point.
  • The compiled code executes the CPU bound activities in-line, using programming primitives.
  • Many I/O and timer related code have native wraps. For example, network I/O.
  • So I/O calls are routed from the script to C++ bridges, with the I/O handle and the completion handler passed as arguments.
  • The native code exercises the libuv loop. It acquires the loop, enqueues a low level event which represents the I/O, and a native callback wrapper into the libuv loop structure.
  • The native code returns to the script - no I/O is taken place at the moment!
  • Items above are repeated many times, until all the non-I/O code are executed, and all the I/O code are registered will the libuv.
  • Finally, when there is nothing left in the system to execute, node pass the control to libuv
  • libuv gets into action, it picks up all the registered events, queries the operating system to get their operability.
  • Those which are are ready for I/O in a non-blocking mode, are picked up, I/O performed, and their callbacks issued. One after the other.
  • Those which are not yet ready (for example a socket read, for which the other end point hasn't written anything yet) will continued to be probed with the OS until they are available.
  • The loop internally maintains an ever increasing timer. When application requests for a deferred callback(such as setTimeout), this internal timer value is leveraged to compute the right time for firing the callback.
  • 有一个线程来承载运行时。
  • 拿起用户脚本。
  • 编译成原生 [ 利用 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)时,会利用此内部计时器值来计算触发回调的正确时间。

While most of the functionalities are catered to in this manner, some (async versions) of the file operations are carried out with the help of additional threads, well integrated into the libuv. While network I/O operations can wait in expectation of an external event such as the other endpoint responding with data etc. the file operations need some work from node itself. For example, if you open a file and wait for the fd to be ready with data, it won't happen, as no one is reading actually! At the same time, if you read from the file inline in the main thread, it can potentially block other activities in the program, and can make visible problems, as file operations are very slow compared to cpu bound activities. So internal worker threads (configurable through UV_THREADPOOL_SIZE environment variable) are employed to operate on files, while the event driven abstraction works intact, from the program's perspective.

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

Hope this helps.

希望这可以帮助。

回答by zangw

An Introduction to libuv

libuv 简介

The node.jsproject began in 2009 as a JavaScript environment decoupled from the browser. Using Google's V8and Marc Lehmann's libev, node.js combined a model of I/O – evented – with a language that was well suited to the style of programming; due to the way it had been shaped by browsers. As node.js grew in popularity, it was important to make it work on Windows, but libev ran only on Unix. The Windows equivalent of kernel event notification mechanisms like kqueue or (e)poll is IOCP. libuv was an abstraction around libev or IOCP depending on the platform, providing users an API based on libev. In the node-v0.9.0 version of libuv libev was removed.

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 中被删除

Also one picture which describe the Event Loop in Node.js by @BusyRich

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



Update 05/09/2017

更新 05/09/2017

Per this doc Node.js event loop,

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

The following diagram shows a simplified overview of the event loop's order of operations.

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

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

note: each box will be referred to as a "phase" of the event loop.

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

Phases Overview

阶段概述

  • timers: this phase executes callbacks scheduled by setTimeout()and setInterval().
  • I/O callbacks: executes almost all callbacks with the exception of close callbacks, the ones scheduled by timers, and setImmediate().
  • idle, prepare: only used internally.
  • poll: retrieve new I/O events; node will block here when appropriate.
  • check: setImmediate()callbacks are invoked here.
  • close callbacks: e.g. socket.on('close', ...).
  • timers:此阶段执行由setTimeout()和调度的回调setInterval()
  • I/O 回调:执行几乎所有回调,除了关闭回调、计时器调度的回调setImmediate().
  • 空闲,准备:仅在内部使用。
  • poll: 检索新的 I/O 事件;节点会在适当的时候阻塞在这里。
  • check:setImmediate()回调在这里被调用。
  • 关闭回调:例如socket.on('close', ...)

Between each run of the event loop, Node.js checks if it is waiting for any asynchronous I/O or timers and shuts down cleanly if there are not any.

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

回答by Peter Hauge

There is one event loop in the NodeJs Architecture.

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

Node.js Event Loop Model

Node.js 事件循环模型

Node applications run in a single-threaded event-driven model. However, Node implements a thread pool in the background so that work can be performed.

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

Node.js adds work to an event queue and then has a single thread running an event loop pick it up. The event loop grabs the top item in the event queue, executes it, and then grabs the next item.

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

When executing code that is longer lived or has blocking I/O, instead of calling the function directly, it adds the function to the event queue along with a callback that will be executed after the function completes. When all events on the Node.js event queue have been executed, the Node.js application terminates.

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

The event loop starts to encouner problems when our application functions block on I/O.

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

Node.js uses event callbacks to avoid having to wait for blocking I/O. Therefore, any requests that perform blocking I/O are performed on a different thread in the background.

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

When an event that blocks I/O is retrieved from the event queue, Node.js retrieves a thread from the thread pool, and executes the function there instead of on the main event loop thread. This prevents the blocking I/O from holding up the rest of the events in the event queue.

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

回答by Warren Zhou

There is only one event loop provided by libuv, V8 is just a JS runtime engine.

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

回答by arunjos007

As a javascript beginner, I had also the same doubt, does NodeJS contains 2 event loops?. After a long research and discussing with one of V8 contributor, I got the following concepts.

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

  • The event loop is a fundamental abstract concept of the JavaScript programming model. So V8 engine provides a default implementation for event loop, which embedders (browser, node) can replace or extend. You guys can find the V8 default implementation of event loop here
  • In NodeJS, there is only one event loop exist, which is provided by the node runtime. The V8 default event loop implementation was replaced with NodeJS event loop implementation
  • 事件循环是 JavaScript 编程模型的一个基本抽象概念。所以 V8 引擎提供了事件循环的默认实现,嵌入器(浏览器、节点)可以替换或扩展. 你们可以在这里找到事件循环的 V8 默认实现
  • 在 NodeJS 中,只有一个事件循环存在,由节点运行时提供。V8 默认事件循环实现被 NodeJS 事件循环实现取代

回答by Daniel

The pbkdf2function has the JavaScript implementation but it actually delegates all the work to be done to the C++ side.

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>);

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

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

The Libuv module has another responsibility that is relevant for some very particular functions in the standard library.

Libuv 模块还有另一个职责,它与标准库中的一些非常特殊的函数相关。

For some standard library function calls, the Node C++ side and Libuv decide to do expensive calculations outside of the event loop entirely.

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

Instead they make use of something called a thread pool, the thread pool is a series of four threads that can be used for running computationally expensive tasks such as the pbkdf2function.

相反,它们使用称为线程池的东西,线程池是一系列四个线程,可用于运行计算成本高的任务,例如pbkdf2函数。

By default Libuv creates 4 threads in this thread pool.

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

In addition to the threads used in the event loop there are four other threads that can be used to offload expensive calculations that need to occur inside our application.

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

Many of the functions included in the Node standard library automatically make use of this thread pool. The pbkdf2function being one of them.

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

The presence of this thread pool is very significant.

这个线程池的存在意义重大。

So Node is not truly single threaded, because there are other threads that Node uses for doing some computationally expensive tasks.

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

If the event pool was responsible for doing the computationally expensive task, then our Node application could do nothing else.

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

Our CPU runs all the instructions inside a thread one by one.

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

By using the thread pool we can do other things inside an event loop while calculations are occurring.

通过使用线程池,我们可以在计算发生时在事件循环内做其他事情。