【问题标题】:Why does a while loop block the event loop?为什么while循环会阻塞事件循环?
【发布时间】:2016-04-21 20:05:54
【问题描述】:

Node.js 书中给出了以下示例:

var open = false;

setTimeout(function() {
  open = true
}, 1000)

while (!open) {
  console.log('wait');
}

console.log('open sesame');

解释为什么while循环会阻塞执行,作者说:

节点永远不会执行超时回调,因为事件循环是 停留在这个 while 循环从第 7 行开始,从来没有给它机会 处理超时事件!

但是,作者没有解释为什么会在事件循环的上下文中发生这种情况,也没有解释到底发生了什么。

有人可以详细说明吗?为什么节点会卡住?以及如何更改上述代码,同时保留while 控制结构,以便不阻塞事件循环并且代码将按照人们可能合理预期的方式运行;等待 在setTimeout 触发之前将仅记录 1 秒,然后在记录“芝麻开门”后进程退出。

诸如关于 IO 和事件循环和回调的答案 to this question 等一般解释并不能真正帮助我合理化这一点。我希望直接引用上述代码的答案会有所帮助。

【问题讨论】:

标签: javascript node.js


【解决方案1】:

真的很简单。在内部,node.js 包含这种类型的循环:

  • 从事件队列中获取一些东西
  • 运行任何指示的任务并运行它直到它返回
  • 当上述任务完成后,从事件队列中获取下一项
  • 运行任何指示的任务并运行它直到它返回
  • 冲洗、起泡、重复 - 一遍又一遍

如果在某个时刻,事件队列中没有任何内容,则进入睡眠状态,直到将某些内容放入事件队列中。


因此,如果一段 Javascript 位于 while() 循环中,则该任务尚未完成,并且按照上述顺序,在之前的任务完全完成之前,不会从事件队列中挑选出任何新内容。所以,一个很长或永远运行的while() 循环只会把工作搞砸。因为 Javascript 一次只运行一个任务(单线程执行 JS),如果该任务在 while 循环中旋转,则其他任何任务都无法执行。

这是一个可能有助于解释的简单示例:

 var done = false;

 // set a timer for 1 second from now to set done to true
 setTimeout(function() {
      done = true;
 }, 1000);

 // spin wait for the done value to change
 while (!done) { /* do nothing */}

 console.log("finally, the done value changed!");

有些人可能从逻辑上认为 while 循环会一直旋转,直到计时器触发,然后计时器会将 done 的值更改为 true,然后 while 循环将结束,最后的 console.log() 将执行.那不是会发生的事情。这实际上是一个无限循环,永远不会执行console.log() 语句。

问题是,一旦您进入while() 循环中的旋转等待,其他任何 Javascript 都无法执行。因此,想要更改done 变量值的计时器无法执行。因此,while 循环条件永远不会改变,因此它是一个无限循环。

以下是 JS 引擎内部发生的情况:

  1. done 变量初始化为 false
  2. setTimeout() 从现在开始安排一个 1 秒的定时器事件
  3. while 循环开始旋转
  4. 在 while 循环旋转 1 秒后,计时器在 JS 引擎内部触发,计时器回调被添加到事件队列中。这可能发生在 JS 引擎内部的不同线程上。
  5. while 循环一直在旋转,因为done 变量永远不会改变。因为它继续旋转,所以 JS 引擎永远不会完成这个线程的执行,也永远不会从事件队列中拉出下一个项目。

node.js 是一个事件驱动的环境。为了在现实世界的应用程序中解决这个问题,done 标志将在未来的某个事件中发生变化。因此,您将在未来为某些相关事件注册一个事件处理程序并在那里完成您的工作,而不是一个旋转的while 循环。在绝对最坏的情况下,您可以设置一个循环计时器和“轮询”来经常检查标志,但在几乎每一种情况下,您都可以为实际事件注册一个事件处理程序,这将导致done 标志改变并做你的工作。正确设计的代码知道其他代码想知道什么时候发生了变化,甚至可以提供自己的事件侦听器和自己的通知事件,人们可以注册感兴趣,甚至只是一个简单的回调。

【讨论】:

  • 感谢@jfriend00。这是一个很好的答案,正是我想要的。我希望其他人也会支持它,而不是你需要代表;)
  • 所以我绕着灌木丛跑了很长时间,while 循环在 js 中阻塞。
  • @marshalcraft - Javascript 块中的任何循环。 Javascript 是单线程和事件驱动的,因此只要循环正在运行,除了循环中的代码之外什么都不能运行。而且,它不仅仅是循环。任何长时间运行的功能都会阻止其他东西运行。单线程 - Javascript 一次只能做一件同步的事情。所有循环都是同步的。万一你错过了,总结在我的第三段之后,它解释了事件队列是如何工作的(这是理解这一切的关键)。我不认为这是一个很长的绕圈子。
  • 我向 jftiend 道歉。我过分批评了。所以非异步函数阻塞。但实际上异步函数中的 while 循环仍然会阻塞。似乎从开始到退出的while循环是一个异步执行单元。我知道js是单线程的。但它不一定内联执行代码,因此在某些情况下它会暂停以继续异步代码或事件。由于它不使用时间来决定执行多少,因此它必须使用“原子”或基本执行单元。比如运行全局范围代码,暂停处理事件或做一些异步函数的工作。
  • 可以想象,js解释器或运行时可以在一个线程上完成。也请原谅我可能糟糕的 js 措辞和术语。我不擅长js。
【解决方案2】:

这是一个很好的问题,但我找到了解决办法!

var sleep = require('system-sleep')
var done = false

setTimeout(function() {
  done = true
}, 1000)

while (!done) {
  sleep(100)
  console.log('sleeping')
}

console.log('finally, the done value changed!')

我认为它有效,因为system-sleep 不是旋转等待。

【讨论】:

  • 我想知道没有人赞成这个页面上唯一可行的解​​决方案,除了所有 PHD 会谈。
  • 我后来发现这是唯一有效的解决方案,但它只有效,因为它是 C++ hack,这在 Node.js 中不应该是可能的 - 这将适用于大多数平台,但是,它会在某些平台上失败,因此应谨慎使用 - 但是非常适合开发人员使用
【解决方案3】:

还有另一种解决方案。几乎每个周期都可以访问事件循环。

let done = false;

setTimeout(() => {
  done = true
}, 5);

const eventLoopQueue = () => {
  return new Promise(resolve => 
    setImmediate(() => {
      console.log('event loop');
      resolve();
    })
  );
}

const run = async () => {
  while (!done) {
    console.log('loop');
    await eventLoopQueue();
  }
}

run().then(() => console.log('Done'));

【讨论】:

    【解决方案4】:

    节点是单个串行任务。没有并行性,它的并发是 IO 绑定的。可以这样想:一切都在单个线程上运行,当您进行阻塞/同步的 IO 调用时,您的进程会暂停,直到返回数据;但是,假设我们有一个线程,而不是等待 IO(读取磁盘、获取 url 等),您的任务会继续执行下一个任务,并在该任务完成后检查该 IO。这基本上就是节点所做的,它是一个“事件循环”,它在循环上轮询 IO 以完成(或进度)。因此,当任务未完成(您的循环)时,事件循环不会进行。简单来说。

    【讨论】:

    • @codecowboy 在 Node.js 中只有一个主线程。因此,所有代码都必须仅由该线程执行。 setTimeout 将安排函数在 1 秒后执行。所以它不会立即执行函数并移动到下一个可执行语句。它找到了一个while 循环。由于它是单线程的,只有当前任务完成后,Node.js 才能从队列中挑选下一项。但随后while 无限运行,因此setTimeout 注册的函数永远没有机会执行。
    【解决方案5】:

    因为定时器需要返回并且正在等待循环完成添加到队列中,所以虽然超时在一个单独的线程中,并且可能确实完成了定时器,但是设置 done = true 的“任务”正在等待无限循环结束

    【讨论】:

      猜你喜欢
      • 2019-02-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-07
      相关资源
      最近更新 更多