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

Why does a while loop block the event loop?

提问人:codecowboy 提问时间:1/16/2016 最后编辑:JJJcodecowboy 更新时间:7/11/2022 访问量:23448

问:

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

var open = false;

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

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

console.log('open sesame');

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

Node 永远不会执行超时回调,因为事件循环是 当循环从第 7 行开始时,一直没有给它机会 处理超时事件!

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

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

笼统的解释,例如关于 IO 和事件循环和回调的问题的答案,并不能真正帮助我合理化这一点。我希望直接引用上述代码的答案会有所帮助。

JavaScript 节点:.js

评论

1赞 thefourtheye 1/16/2016
单线程非阻塞 IO 模型在 Node.js 中的工作原理的可能副本
2赞 codecowboy 1/16/2016
我不同意这是重复的。它引用了一段特定的代码和对这段代码的特定解释。它还询问如何重写代码。

答:

2赞 tsturzl 1/16/2016 #1

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

评论

2赞 thefourtheye 1/16/2016
@codecowboy 在 Node 中.js只有一个主线程。因此,所有代码只能由该线程执行。将调度函数在 1 秒后执行。因此,它不会立即执行该函数并移动到下一个可执行语句。它找到一个循环。由于它是单线程的,只有当当前任务完成时,Node.js 才能从队列中选择下一个项目。但是,它无限地运行,因此注册的函数永远没有机会执行。setTimeoutwhilewhilesetTimeout
51赞 jfriend00 1/16/2016 #2

这真的很简单。在内部,node.js 由以下类型的循环组成:

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

如果在某个时候,事件队列中没有任何内容,则进入睡眠状态,直到事件队列中放置了某些内容或直到触发计时器为止。


因此,如果一段 Javascript 处于循环中,则该任务尚未完成,并且按照上述顺序,在前一个任务完全完成之前,不会从事件队列中挑选出任何新内容。因此,一个很长或永远运行的循环只会使作品变得胶着。因为 Javascript 一次只运行一个任务(用于 JS 执行的单线程),如果这个任务在 while 循环中旋转,那么其他任何任务都无法执行。while()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 循环会一直旋转,直到计时器触发,然后计时器会更改 to 的值,然后 while 循环将完成,at end 将执行。那不是将要发生的事情。这实际上是一个无限循环,语句永远不会被执行。donetrueconsole.log()console.log()

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

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

  1. done变量初始化为false
  2. setTimeout()从现在开始将计时器事件安排 1 秒
  3. while 循环开始旋转
  4. 在 while 循环旋转 1 秒后,计时器已准备好触发,但在解释器返回事件循环之前,它实际上无法执行任何操作
  5. while 循环不断旋转,因为变量永远不会改变。由于它继续旋转,JS 引擎永远不会完成此执行线程,并且永远不会从事件队列中拉取下一项或运行挂起的计时器。done

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

评论

0赞 codecowboy 1/16/2016
谢谢@jfriend00。这是一个很好的答案,正是我想要的。我希望其他人也会投赞成票,而不是说你需要代表;)
0赞 marshal craft 9/8/2017
所以我在灌木丛中跑了很长一段路,而循环在 js 中阻塞了。
0赞 jfriend00 9/8/2017
@marshalcraft - Javascript 块中的 ANY 循环。Javascript 是单线程和事件驱动的,因此只要循环在运行,除了循环中的代码之外,其他任何东西都无法运行。而且,这不仅仅是循环。任何长时间运行的函数都会阻止其他事物运行。单线程 - Javascript 一次只能执行一项同步操作。所有循环都是同步的。如果你错过了它,摘要在我的第三段中,它解释了事件队列的工作原理(这是理解这一切的关键)。我不认为这是绕着灌木丛跑很长的时间。
0赞 marshal craft 9/8/2017
我向jftiend道歉。我太挑剔了。所以非异步函数块。但实际上,异步函数中的 while 循环仍然会阻塞。似乎从开始到退出的 while 循环是异步执行的单个单元。我知道js是单线程的。但它不一定以内联方式执行代码,因此在某些情况下它会暂停以继续异步代码或事件。由于它不使用时间来决定执行多少,因此它必须使用“原子”或基本执行单位。比如运行一下全局范围代码,暂停处理事件或对异步函数做一些工作。
0赞 marshal craft 9/8/2017
可以想象,js 解释器或运行时可以在一个线程上执行此操作。也请原谅我可能很差的 js 措辞和术语。我不擅长js。
8赞 danday74 9/10/2017 #3

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

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

评论

3赞 jolly 10/19/2017
我想知道除了所有的博士演讲之外,没有人对这个页面上唯一有效的解决方案投赞成票。
2赞 danday74 10/19/2017
我后来发现这是唯一有效的解决方案,但它之所以有效,只是因为它是 C++ 黑客,这在 Node.js 中应该是不可能的 - 这将适用于大多数平台,但是,它会在某些平台上失败,因此应谨慎使用 - 非常适合开发使用
0赞 Ziyi Wang 2/7/2018 #4

因为 Timer 需要 BACKBACK 并且正在等待循环完成以添加到队列中,所以虽然超时在一个单独的线程中,并且可能确实对 timer 进行了调整,但要设置 done = true 的“任务”正在等待那个无限循环完成

3赞 AlbertS 10/30/2018 #5

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

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'));
0赞 Salah Ben Bouzid 6/18/2022 #6
var open = false;
const EventEmitter = require("events");
const eventEmitter = new EventEmitter();

setTimeout(function () {
  open = true;
  eventEmitter.emit("open_var_changed");
}, 1000);

let wait_interval = setInterval(() => {
console.log("waiting");
}, 100);

eventEmitter.on("open_var_changed", () => {
clearInterval(wait_interval);
console.log("open var changed to  ", open);
});

this exemple works and you can do setInterval and check if the open value changed inside it and it will work