简体   繁体   English

JavaScript 中的事件循环和 Node.js 中的异步非阻塞 I/O 有什么区别?

[英]What is the difference between the event loop in JavaScript and async non-blocking I/O in Node.js?

In this answer to the question -在这个问题的答案中 -

What is non-blocking or asynchronous I/O in Node.js? Node.js 中的非阻塞或异步 I/O 是什么?

the description sounds no different from the event loop in vanilla js.描述听起来与 vanilla js 中的事件循环没有什么不同。 Is there a difference between the two?两者有区别吗? If not, is the Event loop simply re-branded as "Asynchronous non-blocking I/O" to sell Node.js over other options more easily?如果不是,事件循环是否只是简单地重新命名为“异步非阻塞 I/O”,以便更容易地销售 Node.js 而不是其他选项?

The event loop is the mechanism .事件循环是机制 Asynchronous I/O is the goal .异步 I/O 是目标

Asynchronous I/O is a style of programming in which I/O calls do not wait for the operation to complete before returning, but merely arrange for the caller to be notified when that happens, and for the result to be returned somewhere.异步 I/O 是一种编程风格,其中 I/O 调用在返回之前不等待操作完成,而只是安排在发生这种情况时通知调用者,并将结果返回到某个地方。 In JavaScript, the notification is usually performed by invoking a callback or resolving a promise.在 JavaScript 中,通常通过调用回调或解析 promise 来执行通知。 As far as the programmer is concerned, it doesn't matter how this happens: it just does.就程序员而言,这并不重要:它就是这样。 I request the operation, and when it's done, I get notified about it.我请求操作,完成后,我会收到通知。

An event loop is how this is usually achieved.事件循环通常是如何实现的。 The thing is, in most JavaScript implementations, there is literally a loop somewhere that ultimately boils down to:问题是,在大多数 JavaScript 实现中,确实存在一个循环,最终归结为:

while (poll_event(&ev)) {
    dispatch_event(&ev);
}

Performing an asynchronous operation is then done by arranging for the completion of the operation to be received as an event by that loop, and having it dispatch to a callback of the caller's choice.然后通过安排操作的完成作为事件被该循环接收,并让它分派给调用者选择的回调来完成异步操作。

There are ways to achieve asynchronous programming not based on an event loop, for example using threads and condition variables.有一些方法可以实现不基于事件循环的异步编程,例如使用线程和条件变量。 But historical reasons make this programming style quite difficult to realise in JavaScript.但历史原因使得这种编程风格在 JavaScript 中很难实现。 So in practice, the predominant implementation of asynchrony in JavaScript is based on dispatching callbacks from a global event loop.所以在实践中,JavaScript 中异步的主要实现是基于从全局事件循环中调度回调。

From a non-programmer's bird's eye view this may seem like splitting hairs, but the distinction can be occasionally important.从非程序员的鸟瞰角度来看,这可能看起来像分裂的头发,但这种区别有时很重要。

In a traditional Input/Output, when a request comes to a web server, it is assigned to a specific thread.在传统的输入/输出中,当一个请求到达 web 服务器时,它被分配给一个特定的线程。 For each concurrent connection, there is a new thread and the thread will continue to run until a response is sent for a particular request.对于每个并发连接,都有一个新线程,该线程将继续运行,直到为特定请求发送响应。 This is a perfect example of Blocking I/O (Input/Output network operation) because when handling a particular request by a thread there will be some idle time when between operations are being performed such as retrieving a file, opening it, reading it, etc.这是阻塞 I/O(输入/输出网络操作)的完美示例,因为当线程处理特定请求时,在执行操作之间会有一些空闲时间,例如检索文件、打开文件、读取文件、等等

Each of these thread consumes memory and thus, a thread which runs for a longer period of time and also sits ideally for a significant amount of time in between will definitely consume a lot of memory.这些线程中的每一个都消耗 memory,因此,运行较长时间并且理想情况下在其间放置大量时间的线程肯定会消耗大量 memory。 This is one of the main reason that the core APIs in Node.js are built in non-blocking way and support asynchronous I/O.这是 Node.js 中的核心 API 以非阻塞方式构建并支持异步 I/O 的主要原因之一。

Node.js is itself a runtime environment. Node.js 本身就是一个运行时环境。 It follows an observer pattern (also known as Reactor Pattern in other programming languages) which allows each incoming I/O request to be associated and handle within a function or a handler.它遵循观察者模式(在其他编程语言中也称为反应器模式),允许每个传入的 I/O 请求在 function 或处理程序中关联和处理。 This handler function is called a callback function.这个处理程序 function 称为回调 function。

At the time of running a Node.js environment, an Event Loop is initialized which handles the I/O operations by offloading them to an operating system's kernel.在运行 Node.js 环境时,会初始化一个事件循环,该循环通过将 I/O 操作卸载到操作系统的 kernel 来处理 I/O 操作。 The different operating system uses different kernels but their basic mechanism of handling an I/O is similar.不同的操作系统使用不同的内核,但它们处理 I/O 的基本机制是相似的。 These kernels are multi threaded and can handle execution of multiple operations in the background.这些内核是多线程的,可以在后台处理多个操作的执行。 Whenever an I/O operation completes, the Kernel notifies Node.js about it and callback handling that operation will complete its execution.每当 I/O 操作完成时,Kernel 会通知 Node.js 并且回调处理该操作将完成其执行。

All of these operations are queued in a poll which is also known as Event Queue.所有这些操作都在轮询中排队,也称为事件队列。 Any of these operations may proceed to more operations and these new operations are then again added to the Event Queue.这些操作中的任何一个都可以进行更多操作,然后这些新操作会再次添加到事件队列中。 In summary, Event Loop will always be responsible for the execution of all the asynchronous callbacks registered for every event in the Event Queue.总之,事件循环将始终负责执行为事件队列中的每个事件注册的所有异步回调。

At the end I wish to conclude that async I/O is just another name of Non-Blocking just to brand Node.js over others.最后,我想得出结论,异步 I/O 只是非阻塞的另一个名称,只是为了将 Node.js 品牌化。 Event loop is re-branded as Async non-blocking I/O.事件循环被重新命名为异步非阻塞 I/O。

There are 2 different Event Loops:有 2 个不同的事件循环:

  1. Browser Event Loop浏览器事件循环
  2. NodeJS Event Loop NodeJS 事件循环

Browser Event Loop浏览器事件循环

The Event Loop is a process that runs continually, executing any task queued.事件循环是一个持续运行的进程,执行任何排队的任务。 It has multiple task sources which guarantees execution order within that source, but the Browser gets to pick which source to take a task from on each turn of the loop.它有多个任务源,可保证该源内的执行顺序,但浏览器可以在循环的每一轮中选择从哪个源获取任务。 This allows Browser to give preference to performance sensitive tasks such as user-input.这允许浏览器优先考虑性能敏感的任务,例如用户输入。

There are a few different steps that Browser Event Loop checks continuously:浏览器事件循环会持续检查几个不同的步骤:

  • Task Queue - There can be multiple task queues.任务队列- 可以有多个任务队列。 Browser can execute queues in any order they like.浏览器可以按照他们喜欢的任何顺序执行队列。 Tasks in the same queue must be executed in the order they arrived, first in - first out.同一队列中的任务必须按照它们到达的顺序执行,先进先出。 Tasks execute in order, and the Browser may render between tasks.任务按顺序执行,浏览器可能会在任务之间渲染。 Task from the same source must go in the same queue.来自同一来源的任务必须 go 在同一队列中。 The important thing is that task is going to run from start to finish.重要的是该任务将从头到尾运行。 After each task, Event Loop will go to Microtask Queue and do all tasks from there.在每个任务之后,事件循环将 go 到 Microtask Queue 并从那里执行所有任务。

  • Microtasks Queue - The microtask queue is processed at the end of each task.微任务队列- 微任务队列在每个任务结束时处理。 Any additional microtasks queued during during microtasks are added to the end of the queue and are also processed.在微任务期间排队的任何其他微任务都将添加到队列的末尾并进行处理。

  • Animation Callback Queue - The animation callback queue is processed before pixels repaint. Animation 回调队列- animation 回调队列在像素重绘之前处理。 All animation tasks from the queue will be processed, but any additional animation tasks queued during animation tasks will be scheduled for the next frame.队列中的所有 animation 任务都将被处理,但在 animation 任务期间排队的任何其他 animation 任务将被安排到下一帧。

  • Rendering Pipeline - In this step, rendering will happen.渲染管道- 在这一步中,将进行渲染。 The Browser gets to decide when to do this and it tried to be as efficient as possible.浏览器决定何时执行此操作,并尝试尽可能高效。 The rendering steps only happen if there is something actually worth updating.渲染步骤仅在确实有值得更新的内容时才会发生。 The majority of screens update at a set frequency, in most cases 60 times a second (60Hz).大多数屏幕以设定的频率更新,在大多数情况下每秒 60 次 (60Hz)。 So, if we would change page style 1000 times a second, rendering steps would not get processed 1000 times a second, but instead it would synchronize itself with the display and only render up to a frequency display is capable of.因此,如果我们每秒更改页面样式 1000 次,渲染步骤不会每秒处理 1000 次,而是会与显示同步,并且只渲染到显示能够处理的频率。

Important thing to mention are Web APIs, that are effectively threads.值得一提的是 Web API,它们是有效的线程。 So, for example setTimeout() is an API provided to us by Browser.因此,例如setTimeout()是浏览器提供给我们的 API。 When you call setTimeout() Web API would take over and process it, and it will return the result to the main thread as a new task in a task queue.当您调用setTimeout() Web API 将接管并处理它,并将结果作为任务队列中的新任务返回到主线程。

The best video I found that describes how Event Loops works is this one .我发现描述事件循环如何工作的最佳视频是这个 It helped me a lot when I was investigating how Event Loop works.当我研究事件循环的工作原理时,它帮助了我很多。 Another great videos are this one and this one .另一个很棒的视频是这个这个 You should definitely check all of them.您绝对应该检查所有这些。

NodeJS Event Loop NodeJS 事件循环

NodeJS Event Loop allows NodeJS to perform non-blocking operations by offloading operation to the system kernel whenever possible. NodeJS 事件循环允许 NodeJS 通过将操作卸载到系统 kernel 来执行非阻塞操作。 Most modern kernels are multi-threaded and they can perform multiple operations in the background.大多数现代内核都是多线程的,它们可以在后台执行多个操作。 When one of these operations completes, the kernel tells NodeJS.当其中一项操作完成时,kernel 会告诉 NodeJS。

Library that provides the Event Loop to NodeJS is called Libuv.为 NodeJS 提供事件循环的库称为 Libuv。 It will by default create something called Thread Pool with 4 threads to offload asynchronous work to.默认情况下,它会创建一个名为 Thread Pool 的东西,它有 4 个线程来卸载异步工作。 If you want, you can also change the number of threads in the Thread Pool.如果需要,还可以更改线程池中的线程数。

NodeJS Event Loop goes through different phases: NodeJS 事件循环经历了不同的阶段:

  • timers - this phase executes callbacks scheduled by setTimeout() and setInterval() . timers - 此阶段执行由setTimeout()setInterval()安排的回调。

  • pending callbacks - executes I/O callbacks deferred to the next loop iteration.挂起的回调- 执行推迟到下一个循环迭代的 I/O 回调。

  • idle, prepare - only used internally.空闲,准备- 仅在内部使用。

  • poll - retrieve new I/O events; poll - 检索新的 I/O 事件; execute I/O related callbacks (almost all with the exception of close callbacks, the ones scheduled by timers, and setImmediate() ) Node will block here when appropriate.执行与 I/O 相关的回调(几乎所有回调,关闭回调、由计时器调度的回调和setImmediate() )节点将在适当的时候阻塞在这里。

  • check - setImmediate() callbacks are invoked here.检查- 此处调用setImmediate()回调。

  • close callbacks - some close callbacks, eg socket.on('close', ...) .关闭回调- 一些关闭回调,例如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 或计时器,如果没有则干净地关闭。

In Browser, we had Web APIs.在浏览器中,我们有 Web API。 In NodeJS, we have C++ APIs with the same rule.在 NodeJS 中,我们有具有相同规则的 C++ API。

I found this video to be useful if you want to check for more information.如果您想查看更多信息,我发现此视频很有用。

  • For years, JavaScript has been limited to client-side applications such as interactive web applications that run on the browser.多年来,JavaScript 一直仅限于客户端应用程序,例如在浏览器上运行的交互式 web 应用程序。 Using NodeJS, JavaScript can be used to develop server-side applications as well.使用 NodeJS,JavaScript 也可用于开发服务器端应用程序。 Though it's the same programming language which is used in both use cases, client-side and server-side have different requirements.尽管在两个用例中使用相同的编程语言,但客户端和服务器端有不同的要求。

  • Event Loop ” is a generic programming pattern and JavaScript/NodeJS 事件循环”是一种通用编程模式和 JavaScript/NodeJS
    event loops are no different.事件循环也不例外。 The event loop continuously watches for any queued event handlers and will process them accordingly.事件循环持续监视任何排队的事件处理程序,并将相应地处理它们。

  • The “ Events ”, in a browser's context, are user interactions on web 事件”,在浏览器的上下文中,是 web 上的用户交互
    pages (eg, clicks, mouse movements, keyboard events etc.), but in页面(例如,点击、鼠标移动、键盘事件等),但在
    Node's context, events are asynchronous server-side operations (eg, File I/O access, Network I/O etc.)节点的上下文,事件是异步的服务器端操作(例如,文件 I/O 访问、网络 I/O 等)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM