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

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

在此answer问题-

What is non-blocking or asynchronous I/O in Node.js?

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

有 2 种不同的事件循环:

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

浏览器事件循环

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

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

  • 任务队列 - 可以有多个任务队列。浏览器可以按照他们喜欢的任何顺序执行队列。同一个队列中的任务必须按照它们到达的顺序执行,先进先出。任务按顺序执行,浏览器可以在任务之间呈现。来自同一来源的任务必须进入同一队列。重要的是任务将从头到尾进行 运行。在每个任务之后,Event Loop 将转到 Microtask Queue 并从那里执行所有任务。

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

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

  • 渲染管线 - 在此步骤中,将进行渲染。浏览器决定何时执行此操作,并尝试尽可能高效。渲染步骤只有在确实有值得更新的东西时才会发生。大多数屏幕以设定的频率更新,在大多数情况下为每秒 60 次 (60Hz)。因此,如果我们每秒更改页面样式 1000 次,渲染步骤将不会每秒处理 1000 次,而是将自身与显示同步,并且只呈现显示能够达到的频率。

值得一提的是 Web APIs,它们实际上是线程。因此,例如 setTimeout() 是浏览器提供给我们的 API。当你调用 setTimeout() 时,Web API 将接管并处理它,并将 return 结果作为任务队列中的新任务发送到主线程。

我发现描述事件循环如何工作的最好的视频是 this one. It helped me a lot when I was investigating how Event Loop works. Another great videos are this one and this one。你绝对应该检查所有这些。

NodeJS 事件循环

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

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

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

  • timers - 此阶段执行由 setTimeout()setInterval() 安排的回调。

  • 待处理的回调 - 执行I/O 延迟到下一个循环迭代的回调。

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

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

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

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

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

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

如果您想查看更多信息,我发现 this video 很有用。

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

异步 I/O 是一种编程风格,其中 I/O 调用不等待操作完成再返回,而只是安排在发生这种情况时通知调用者,并且对于结果返回某处。在 JavaScript 中,通常通过调用回调或解决承诺来执行通知。就程序员而言,这是如何发生的并不重要:它就是发生了。我请求操作,当它完成时,我会收到通知。

通常是通过事件循环来实现的。问题是,在大多数 JavaScript 实现中,某处确实存在一个循环,最终归结为:

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

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

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

换句话说,“事件循环”描述了主机所做的事情,而“异步 I/O”描述了程序员所做的事情。

从非程序员的角度来看,这似乎是一针见血,但这种区别有时很重要。

  • 多年来,JavaScript一直局限于客户端应用程序 例如在浏览器上 运行 的交互式 Web 应用程序。使用 NodeJS, JavaScript 可用于开发服务器端应用程序 出色地。尽管两者使用的是相同的编程语言 用例,客户端和服务器端有不同的要求。

  • 事件循环”是一种通用编程模式,JavaScript/NodeJS
    事件循环也不例外。事件循环持续监视 任何排队的事件处理程序并将相应地处理它们。

  • 事件”,在浏览器上下文中,是网络上的用户交互
    页面(例如,点击、鼠标移动、键盘事件等),但在
    节点的上下文,事件是异步的服务器端操作(例如, 文件 I/O 访问,网络 I/O 等)