【问题标题】:Busy waiting in NodeJS在 NodeJS 中忙于等待
【发布时间】:2020-09-22 03:30:48
【问题描述】:

有两个异步函数。一个会设置一个变量,另一个会等待变量改变,如下面的代码所示。即使通常的做法是使用 事件或承诺,在我的情况下是不可行的。

function delay(ms: number): Promise<void> {
  return new Promise(resolve => {
    setTimeout(() => { resolve(); }, ms);
  });
}

function async fn1() {
  let done = false;
  (async () => {
    await delay(100); // simulate some work
    done = true;
  })();
  await (async () => {
    while (!done) { }
    // do other things
  })();
}

function async fn2() {
  let done = false;
  (async () => {
    await delay(100); // simulate some work
    done = true;
  })();
  await (async () => {
    while (!done) {
      await delay(200); // introduce some extra delay
    }
    // do other things
  })();
}

我想知道为什么fn1 会卡在while 循环中,但fn2 会起作用。 因为它们之间的唯一区别是后者 引入了一些额外的等待。这是否与一些潜在的 NodeJS/V8解释器的线程调度机制?

我使用的是 Node 12.18.3 和 TypeScript 4.0.3。

编辑:修正fn2 的定义。

【问题讨论】:

  • while (!done) { } // do other things } 运行期间,除非donedo other things 中设置为true,否则不会执行其他任何操作...因为调用堆栈永远不会为空
  • 基本上 js 是单线程的,如果有什么东西让它像你的忙循环一样被阻塞,它不能去其他工作,除非它暂停
  • 查找 javascript 事件队列或 javascript 调用堆栈,以很好地解释这一切在 JS 中是如何工作的
  • 您知道,对于像 nodejs 这样的事件驱动的单线程环境来说,繁忙的等待循环通常是错误的设计类型。有时可以使它们与 Promise 和 await 一起工作,但即便如此,最好还是使用一个在事件发生时解决的 Promise 并在没有任何繁忙等待循环的情况下监听它。如果我们看到的是真实的、实际的代码,而不是这个实际上并没有做任何有用的事情并且不是现实世界问题的伪代码,我们可以通过一个好的设计更好地帮助您。
  • 所以,总而言之,这是一条错误的设计路径。向我们展示一个真实世界的问题(不是伪代码),我们将帮助您以正确的方式设计该实际问题。

标签: javascript node.js


【解决方案1】:

Javascript 是单线程‡。因此任何形式的忙等待都会导致线程完全阻塞,阻止它进入事件循环,这意味着没有事件被处理。

在 C 级别,解释器非常简单。由于它是单线程的,它不需要任何复杂的逻辑,只需要一个无限的while循环。在伪代码中是这样的:

do {

    event_queue = execute_javascript()

    completed_events = wait_for_events()

    for_each (event from completed_events) {

        this_event = event_queue.find_one_matching(event)

        execute_javascript(this_event.callback)
    }

} while (there_is_pending_events)

如您所见,解释器中没有任何东西是并行运行的。它们都按顺序运行。异步代码等待并行但不并行执行。

在您的代码中,while(){} 循环卡在 execute_javascript() 中,直到您的 while 循环完成后才会返回。这意味着解释器被卡住了。

while(){} 循环内添加await 会导致execute_javascript() 在等待处返回(并将在await 之后的行再次继续),从而允许解释器继续等待wait_for_events() 处的事件。

‡ 注意:有些人会告诉你 javascript 使用后台线程来处理异步代码。他们错了。 Node.js(不是浏览器)确实使用一个、单个、附加的线程来处理磁盘 I/O,但只使用磁盘 I/O。网络 I/O 是在我上面描述的主线程中完成的。磁盘 I/O 是这样完成的,因为 Windows、Linux 和 BSD(BSD 包括 Mac OSX)之间的异步磁盘 I/O API 兼容性非常低

如果您对事件循环和异步代码执行在 javascript 中的工作原理的低级解释感兴趣,您可能会对我对这些其他相关问题的回答感兴趣。在一些我解释到硬件如何处理它的级别。在一些我探索它是如何在 C/C++ 中完成的:

Is my understanding of asynchronous operations correct?

Is there any other way to implement a "listening" function without an infinite while loop?

node js - what happens to incoming events during callback excution

I know that callback function runs asynchronously, but why?

【讨论】:

    【解决方案2】:

    对于第一个函数fn1,你把async放在函数前面,但是fn2你没有。尝试从第一个函数中删除它,看看它是否仍然卡住。

    【讨论】:

    • 对不起,这是我的错误。 fn2 确实有 async 修饰符,我忘了输入。如果async 缺失,原始代码将无法编译。
    猜你喜欢
    • 2013-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-08
    • 1970-01-01
    • 2013-09-29
    • 2015-07-04
    • 2015-03-13
    相关资源
    最近更新 更多