JavaScript 中的事件循环和 Node.js 中的异步非阻塞 I/O 有啥区别?
Posted
技术标签:
【中文标题】JavaScript 中的事件循环和 Node.js 中的异步非阻塞 I/O 有啥区别?【英文标题】:What is the difference between the event loop in JavaScript and async non-blocking I/O in Node.js?JavaScript 中的事件循环和 Node.js 中的异步非阻塞 I/O 有什么区别? 【发布时间】:2022-01-09 00:52:13 【问题描述】:在这个answer的问题上-
什么是 Node.js 中的非阻塞或异步 I/O?
描述听起来与原版 js 中的事件循环没有什么不同。两者有区别吗?如果不是,事件循环是否只是简单地重新命名为“异步非阻塞 I/O”,以便更轻松地销售 Node.js 而不是其他选项?
【问题讨论】:
是的! “绿色线程”是一个相对较新的概念,它通过 NodeJS 引入服务器(当时在 Java、C# 等中并不常见)。它将事件循环概念从 javascript 引入服务器运行时。 嗯...我不认为事件循环被简单地重新命名为“异步非阻塞 I/O”。在这两种情况下,事件循环的目的是相同的,但回调队列是浏览器的 Web API(Vanilla js)和 C/C++ API,即 libuv 库——专注于 Node.js 的异步 I/O。所以我相信 libuv 库是这里的关键......我从 Philip Roberts 的视频中选择了理解的概念 - youtube.com/watch?v=8aGhZQkoFbQ。 【参考方案1】:事件循环是机制。异步 I/O 是目标。
异步 I/O 是一种编程风格,其中 I/O 调用在返回之前不会等待操作完成,而只是安排在发生这种情况时通知调用者,并将结果返回到某个地方.在 JavaScript 中,通知通常通过调用回调或解析承诺来执行。就程序员而言,它是如何发生的并不重要:它就是这样。我请求操作,完成后,我会收到通知。
事件循环通常是如何实现的。问题是,在大多数 JavaScript 实现中,确实存在一个循环,最终归结为:
while (poll_event(&ev))
dispatch_event(&ev);
然后执行异步操作,方法是安排该循环将操作的完成作为事件接收,并将其分派给调用者选择的回调。
有一些方法可以实现不基于事件循环的异步编程,例如使用线程和条件变量。但是历史原因使得这种编程风格很难在 JavaScript 中实现。所以在实践中,JavaScript 中异步的主要实现是基于从全局事件循环中调度回调。
换句话说,“事件循环”描述了主机做什么,而“异步 I/O”描述了程序员做什么。
从非程序员的鸟瞰角度来看,这可能看起来像分裂的头发,但这种区别有时很重要。
【讨论】:
【参考方案2】:有 2 个不同的事件循环:
-
浏览器事件循环
NodeJS 事件循环
浏览器事件循环
事件循环是一个持续运行的进程,执行任何排队的任务。它有多个任务源来保证该源中的执行顺序,但是浏览器可以在循环的每一轮中选择从哪个源获取任务。这允许浏览器优先考虑性能敏感的任务,例如用户输入。
浏览器事件循环会持续检查几个不同的步骤:
任务队列 - 可以有多个任务队列。浏览器可以按照他们喜欢的任何顺序执行队列。同一队列中的任务必须按照它们到达的顺序执行,先进先出。任务按顺序执行,浏览器可能会在任务之间渲染。来自同一来源的任务必须进入同一队列。重要的是该任务将从头到尾运行。每个任务完成后,Event Loop 会进入 Microtask Queue 并从那里执行所有任务。
微任务队列 - 微任务队列在每个任务结束时处理。在微任务期间排队的任何其他微任务都将添加到队列的末尾并进行处理。
动画回调队列 - 在像素重绘之前处理动画回调队列。队列中的所有动画任务都将被处理,但在动画任务期间排队的任何其他动画任务将被安排到下一帧。
渲染管道 - 在这一步中,将进行渲染。浏览器决定何时执行此操作,并尝试尽可能高效。渲染步骤仅在确实有值得更新的内容时才会发生。大多数屏幕以设定的频率更新,在大多数情况下每秒 60 次 (60Hz)。因此,如果我们每秒更改页面样式 1000 次,则渲染步骤不会每秒处理 1000 次,而是会与显示器同步,并且只渲染到显示器能够处理的频率。
值得一提的是 Web API,它们实际上是线程。因此,例如setTimeout()
是浏览器提供给我们的 API。当你调用setTimeout()
时,Web API 会接管并处理它,并将结果作为任务队列中的新任务返回给主线程。
我发现描述事件循环如何工作的最佳视频是this one。当我研究事件循环的工作原理时,它帮助了我很多。另一个很棒的视频是this one 和this one。您绝对应该检查所有这些。
NodeJS 事件循环
NodeJS 事件循环允许 NodeJS 通过尽可能将操作卸载到系统内核来执行非阻塞操作。大多数现代内核都是多线程的,它们可以在后台执行多个操作。当这些操作之一完成时,内核会告诉 NodeJS。
为 NodeJS 提供事件循环的库称为 Libuv。默认情况下,它会创建一个名为 Thread Pool 的东西,它有 4 个线程来卸载异步工作。如果需要,还可以更改线程池中的线程数。
NodeJS 事件循环经历了不同的阶段:
计时器 - 此阶段执行由setTimeout()
和setInterval()
安排的回调。
挂起的回调 - 执行推迟到下一个循环迭代的 I/O 回调。
空闲,准备 - 仅在内部使用。
poll - 检索新的 I/O 事件;执行 I/O 相关的回调(除了关闭回调、定时器调度的回调和setImmediate()
之外几乎所有)节点会在适当的时候阻塞在这里。
检查 - 在此处调用setImmediate()
回调。
关闭回调 - 一些关闭回调,例如socket.on('close', ...)
.
在事件循环的每次运行之间,Node.js 会检查它是否正在等待任何异步 I/O 或计时器,如果没有则干净地关闭。
在浏览器中,我们有 Web API。在 NodeJS 中,我们有具有相同规则的 C++ API。
如果您想查看更多信息,我发现 this video 很有用。
【讨论】:
【参考方案3】:多年来,JavaScript 一直仅限于客户端应用程序 例如在浏览器上运行的交互式 Web 应用程序。使用 NodeJS,JavaScript 可用于开发服务器端应用程序 好吧。虽然它是在两者中使用的相同的编程语言 用例,客户端和服务器端有不同的要求。
“事件循环”是一种通用编程模式和 JavaScript/NodeJS 事件循环也不例外。事件循环不断地监视 任何排队的事件处理程序,并将相应地处理它们。
在浏览器的上下文中,“事件”是网络上的用户交互 页面(例如,点击、鼠标移动、键盘事件等),但在 节点的上下文,事件是异步的服务器端操作(例如, 文件 I/O 访问、网络 I/O 等)
【讨论】:
正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center。以上是关于JavaScript 中的事件循环和 Node.js 中的异步非阻塞 I/O 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章