【问题标题】:NodeJS Event Loop FundamendalsNodeJS 事件循环基础
【发布时间】:2015-08-13 17:55:58
【问题描述】:

我确定这是一个常见问题,但没有找到具体答案。

我有点理解 NodeJS 的基本概念,以及它处理 I/O 的异步/非阻塞性质。

为了论证,让我们举一个简单的例子,一个用 node 编写的 HTTP 服务器,它执行 unix 命令 'find /' 并将结果写入 http 响应(因此在用户的浏览器上显示命令的结果)。 假设这需要 3 秒。

假设有两个用户“A”和“B”同时通过他们的浏览器请求。

据我了解,用户的请求在事件队列(消息 A、消息 B)中排队。该消息还具有对其关联的回调的引用,一旦处理完成就会执行。

由于事件循环是单线程的,并且会一一处理事件,

在我上面的例子中,触发“用户 B”的回调需要 6 秒吗? [3个用于“用户A”的事件处理,3个用于它自己的事件处理]

这听起来好像我在这里遗漏了什么?

最糟糕的是,如果有 100 个用户在同一毫秒请求?第 100 位活动所有者将成为最不幸的用户,必须等待永恒。

据我了解,运行时只有一个事件队列,上述问题适用于应用程序任何部分的任何用户。例如,网页 X 中的慢速数据库查询会减慢网页 Y 中的其他用户的速度?

从根本上说,我发现事件的串行处理和相关回调的串行执行存在问题。

我错过了什么吗?

【问题讨论】:

  • 当 node 告诉 shell 执行find / 时,它只会启动一个新进程。您可以同时运行多个进程(在节点之外)。节点的异步部分只是意味着它将等待这些操作完成,同时继续处理其他事情,然后开始服务。 A 和 B 都会等待大约 3 秒的响应。
  • 感谢您的回复,所以如果为每个请求启动一个新进程,我的理解是否正确:“事件处理将并行发生,但这些事件处理的回调是串行的” .因此,事件循环将被阻止的唯一实例是在回调中具有“睡眠功能”。
  • 如果您执行同步操作,该进程将被阻止,例如,fs.readFileSync。此外,在这种情况下,事件处理实际上只并行发生,因为节点进程没有这样做。
  • 嗯好的。如果必须将其扩展到数据库查询示例,db.executeQuery('select * from employees', function(rows) { // Do something with rows. }); NodeJS 的职责只是提供一个基础设施来以非阻塞(或异步)方式执行数据库查询。现在由数据库(库或模块)负责在新线程中运行查询以避免事件处理被阻塞。这是一个公平的说法吗?
  • 是的,它就是这样工作的。查询将由数据库执行。然后,一旦数据库响应,节点将处理回调:function (rows)

标签: javascript node.js dom-events event-loop


【解决方案1】:

正确编写的 node.js 服务器将对任何网络、磁盘 I/O、计时器或与其他进程的通信使用异步 I/O 和通信。以这种方式编写时,可以并行处理多个 http 请求。虽然处理任何给定请求的 node.js 代码一次只运行一个,但只要一个请求等待 I/O(这通常是请求的大部分时间),其他请求就可以运行。

最终结果是所有请求似乎都在同时进行(尽管实际上,对它们的工作是交织在一起的)。 Javascript 事件队列是在所有各种请求之间序列化工作的机制。每当异步操作完成它的工作或希望将某个事件通知主 JS 线程时,它都会将某些内容放入事件队列中。当当前 JS 执行线程完成时(即使它有自己的异步操作正在进行中),JS 引擎会查看事件队列,然后执行该队列中的下一项(通常是某种形式的回调),并且在那个这样,下一个排队的操作就会继续。

在您的具体示例中,当您启动另一个进程然后异步等待其结果时,当前执行线程完成,然后事件队列中的下一个项目开始运行。如果下一项是另一个 http 请求,则该请求开始处理。当第二个请求到达某个异步点时,它的执行线程完成,并且事件队列中的下一个项目再次运行。通过这种方式,新的 http 请求开始启动,并且来自已完成的异步操作的异步回调开始运行。事情的发生大致按照 FIFO(先进先出)的顺序排列在事件队列中。我说“大致”是因为实际上有不同类型的事件,并且并非所有事件都被平等地序列化,但出于讨论的目的,可以忽略实现细节。

因此,如果三个 http 请求同时到达,那么其中一个请求将一直运行,直到达到异步点。然后,下一个将运行,直到它达到一个异步点。然后,第三个将运行,直到它达到一个异步点。然后,无论哪个请求完成了它的第一个异步操作,都会从该异步操作中获得一个回调,并且它将一直运行直到它完成或到达另一个异步点。等等……

由于通常会导致 Web 服务器花费大量时间来响应的大部分原因通常是某种 I/O 操作(磁盘或网络),这些操作都可以在 node.js 中异步编程,因此整个过程通常运行良好并且它实际上比使用每个请求使用单独的线程更有效地使用服务器资源。它不能很好地工作的一次是如果有大量计算密集型或一些长时间运行,但不是长时间占用主 node.js 线程的异步操作。因为 node.js 系统是一个协作的 CPU 共享系统,如果你有一个长时间运行的操作会占用 node.js 主线程,它会占用系统(与其他操作根本没有抢占式共享)可能与多线程系统一起使用)。占用系统会使所有其他请求等到第一个请求完成。 node.js 对一些 CPU 占用计算的回答是将一个操作移动到另一个进程,并从 node.js 线程与另一个进程异步通信 - 从而保留单个 node.js 线程的异步模型。


对于node.js数据库操作,数据库一般会提供一个异步接口供node.js编程以异步方式使用数据库,然后由数据库接口的实现来实际实现接口异步时尚。这很可能通过与实现实际数据库逻辑的其他进程通信(可能通过 TCP 通信)来完成。实际的数据库逻辑可能使用或不使用实际的线程 - 这是由数据库本身决定的实现细节。对 node.js 来说重要的是计算和数据库工作在其他进程中的 node.js 线程之外,甚至可能在另一台主机上,因此它不会阻塞 node.js 线程。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-09
    • 2020-02-07
    • 2017-06-28
    相关资源
    最近更新 更多