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

Fla*_*dun 17 javascript event-loop node.js nodejs-server

在这个问题的回答中——

Node.js 中的非阻塞或异步 I/O 是什么?

这个描述听起来与普通 js 中的事件循环没有什么不同。两者有区别吗?如果不是,事件循环是否只是简单地重新命名为“异步非阻塞 I/O”,以便更容易地销售 Node.js 而不是其他选项?

use*_*170 11

事件循环就是这种机制。异步 I/O 是目标

\n

异步 I/O 是一种编程风格,其中 I/O 调用在返回之前不等待操作完成,而只是安排在发生这种情况时通知调用者,并将结果返回到某处。在 JavaScript 中,通知通常通过调用回调或解析 Promise 来执行。对于程序员来说,这如何发生并不重要:它就是这样。我请求该操作,当它\xe2\x80\x99 完成时,我会收到通知。

\n

事件循环是通常实现这一点的方式。问题是,在大多数 JavaScript 实现中,实际上在某处存在一个循环,最终归结为:

\n
while (poll_event(&ev)) {\n    dispatch_event(&ev);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后,通过安排操作的完成作为事件由该循环接收,并将其分派到调用者\xe2\x80\x99s 选择的回调来完成异步操作。

\n

有多种方法可以实现不基于事件循环的异步编程,例如使用线程和条件变量。但历史原因使得这种编程风格在 JavaScript 中实现起来相当困难。因此,在实践中,JavaScript 中异步的主要实现是基于从全局事件循环中调度回调。

\n

换句话说,\xe2\x80\x98事件循环\xe2\x80\x99描述了主机做什么,而\xe2\x80\x98异步I/O\xe2\x80\x99描述了程序员做什么。

\n

从非程序员\xe2\x80\x99s鸟\xe2\x80\x99s的角度来看,这可能看起来像是吹毛求疵,但这种区别有时可能很重要。

\n


Nen*_*vic 8

有 2 种不同的事件循环:

  1. 浏览器事件循环
  2. NodeJS 事件循环

浏览器事件循环

事件循环是一个持续运行的进程,执行排队的任何任务。它有多个任务源,保证该源内的执行顺序,但浏览器可以在循环的每一轮中选择从哪个源获取任务。这允许浏览器优先处理性能敏感的任务,例如用户输入。

浏览器事件循环会持续检查几个不同的步骤:

  • 任务队列- 可以有多个任务队列。浏览器可以按照它们喜欢的任何顺序执行队列。同一队列中的任务必须按照到达的顺序执行,先进先出。任务按顺序执行,浏览器可以在任务之间进行渲染。来自同一源的任务必须进入同一队列。重要的是任务将从头到尾运行。每个任务完成后,事件循环将进入微任务队列并从那里执行所有任务。

  • 微任务队列- 微任务队列在每个任务结束时进行处理。微任务期间排队的任何其他微任务都会添加到队列末尾并进行处理。

  • 动画回调队列- 动画回调队列在像素重绘之前处理。队列中的所有动画任务都将被处理,但在动画任务期间排队的任何其他动画任务将被安排到下一帧。

  • 渲染管道- 在此步骤中,将进行渲染。浏览器可以决定何时执行此操作,并且它会尝试尽可能高效。仅当确实存在值得更新的内容时,才会执行渲染步骤。大多数屏幕以设定频率更新,大多数情况下每秒 60 次 (60Hz)。因此,如果我们每秒更改页面样式 1000 次,则渲染步骤不会每秒处理 1000 次,而是会与显示同步,并且仅渲染至显示能够执行的频率。

值得一提的是 Web API,它们实际上是线程。例如,setTimeout()浏览器向我们提供了一个 API。当你调用setTimeout()Web API时,它会接管并处理它,并将结果作为任务队列中的新任务返回到主线程。

我发现描述事件循环如何工作的最好的视频是这个。当我研究事件循环如何工作时,它对我帮助很大。另一个很棒的视频是这个这个。您绝对应该检查所有这些。

NodeJS 事件循环

NodeJS 事件循环允许 NodeJS 通过尽可能将操作卸载到系统内核来执行非阻塞操作。大多数现代内核都是多线程的,它们可以在后台执行多个操作。当这些操作之一完成时,内核会通知 NodeJS。

为 NodeJS 提供事件循环的库称为 Libuv。默认情况下,它将创建一个名为“线程池”的东西,其中有 4 个线程来卸载异步工作。如果需要,您还可以更改线程池中的线程数。

NodeJS 事件循环经历不同的阶段:

  • 计时器setTimeout()- 此阶段执行由和安排的回调setInterval()

  • 挂起的回调- 执行推迟到下一个循环迭代的 I/O 回调。

  • 空闲,准备-仅在内部使用。

  • poll - 检索新的 I/O 事件;执行 I/O 相关的回调(几乎所有回调,关闭回调除外,由计时器调度的回调),setImmediate()Node 将在适当的时候阻塞在这里。

  • check -setImmediate()此处调用回调。

  • 关闭回调- 一些关闭回调,例如socket.on('close', ...).

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

在浏览器中,我们有 Web API。在 NodeJS 中,我们有具有相同规则的 C++ API。

如果您想了解更多信息,我发现此视频非常有用。