为什么while循环会阻塞事件循环?

时间:2016-01-16 07:03:37

标签: javascript node.js

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

var open = false;

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

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

console.log('open sesame');

解释while循环阻止执行的原因,作者说:

  

Node永远不会执行超时回调,因为事件循环是   在第7行开始循环时,坚持这一点,永远不会给它机会   处理超时事件!

然而,作者没有解释为什么会在事件循环的上下文中发生这种情况或者真正发生在幕后的事情。

有人可以详细说明吗?节点为什么会卡住?如何更改上面的代码,同时保留while控制结构,以便不阻止事件循环,代码将按照人们可能合理预期的方式运行;等待 将在setTimeout点火前记录1秒,然后在记录'打开芝麻'后退出该过程。

关于IO和事件循环以及回调的答案to this question等通用解释并没有真正帮助我理解这一点。我希望直接引用上述代码的答案会有所帮助。

5 个答案:

答案 0 :(得分:20)

真的很简单。在内部,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循环将结束{{1}最后将执行。那不会发生什么。这实际上是一个无限循环,永远不会执行console.log()语句。

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

这里是JS引擎内部发生的事情:

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

答案 1 :(得分:2)

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

答案 2 :(得分:2)

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

int

我认为它有效,因为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!') 不是旋转等待。

答案 3 :(得分:1)

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

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 :(得分:0)

因为计时器需要回来并且正在等待循环才能完成添加到队列,所以虽然超时是在一个单独的线程中,并且可能确实finihed计时器,但"任务" to set done = true正在等待无限循环完成