提问人:codecowboy 提问时间:1/16/2016 最后编辑:JJJcodecowboy 更新时间:7/11/2022 访问量:23448
为什么 while 循环会阻塞事件循环?
Why does a while loop block the event loop?
问:
以下示例在 Node.js 书中给出:
var open = false;
setTimeout(function() {
open = true
}, 1000)
while (!open) {
console.log('wait');
}
console.log('open sesame');
在解释为什么 while 循环会阻止执行时,作者说:
Node 永远不会执行超时回调,因为事件循环是 当循环从第 7 行开始时,一直没有给它机会 处理超时事件!
但是,作者没有解释为什么这会发生在事件循环的上下文中,也没有解释引擎盖下到底发生了什么。
有人可以详细说明一下吗?为什么节点会卡住?以及如何更改上述代码,同时保留控制结构,以便事件循环不会被阻塞,并且代码将按照人们的合理预期运行;等
在火灾发生前仅记录 1 秒钟,然后在记录“芝麻开门”后退出该过程。while
setTimeout
笼统的解释,例如关于 IO 和事件循环和回调的问题的答案,并不能真正帮助我合理化这一点。我希望直接引用上述代码的答案会有所帮助。
答:
节点是单个串行任务。没有并行性,其并发性是 IO 绑定的。可以这样想:一切都在单个线程上运行,当您进行阻塞/同步的 IO 调用时,您的进程会停止,直到返回数据;但是,假设我们有一个线程,而不是等待 IO(读取磁盘、获取 url 等),您的任务继续执行下一个任务,并在该任务完成后检查该 IO。这基本上就是节点的作用,它是一个“事件循环”,它轮询 IO 以完成(或进度)循环。因此,当任务没有完成(你的循环)时,事件循环就不会进行。简单地说。
评论
setTimeout
while
while
setTimeout
这真的很简单。在内部,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 将执行。那不是将要发生的事情。这实际上是一个无限循环,语句永远不会被执行。done
true
console.log()
console.log()
问题是,一旦你进入循环中的旋转等待,就没有其他 Javascript 可以执行。因此,想要更改变量值的计时器无法执行。因此,while 循环条件永远不会改变,因此它是一个无限循环。while()
done
以下是 JS 引擎内部发生的情况:
done
变量初始化为false
setTimeout()
从现在开始将计时器事件安排 1 秒- while 循环开始旋转
- 在 while 循环旋转 1 秒后,计时器已准备好触发,但在解释器返回事件循环之前,它实际上无法执行任何操作
- while 循环不断旋转,因为变量永远不会改变。由于它继续旋转,JS 引擎永远不会完成此执行线程,并且永远不会从事件队列中拉取下一项或运行挂起的计时器。
done
Node.js 是一个事件驱动的环境。为了在现实世界的应用程序中解决这个问题,标志将在未来的某个事件中更改。因此,您将为将来的某些相关事件注册一个事件处理程序,并在那里执行工作,而不是旋转循环。在绝对最坏的情况下,您可以设置一个重复计时器并“轮询”以经常检查标志,但在几乎每种情况下,您都可以为将导致标志更改的实际事件注册一个事件处理程序,并在其中完成您的工作。设计得当的代码知道其他代码想知道某些内容何时发生了变化,甚至可以提供自己的事件侦听器和自己的通知事件,人们可以对此感兴趣,甚至只是一个简单的回调。done
while
done
评论
这是一个很好的问题,但我找到了解决方法!
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
评论
因为 Timer 需要 BACKBACK 并且正在等待循环完成以添加到队列中,所以虽然超时在一个单独的线程中,并且可能确实对 timer 进行了调整,但要设置 done = true 的“任务”正在等待那个无限循环完成
还有另一种解决方案。您几乎可以访问每个周期的事件循环。
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'));
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
评论