[英]Synchronizing loop in Node.js with setTimeout - strange, apparently intentional behavior
我正在研究生产中正在运行的性能不佳的Node.js代码段,并且看到了这个奇怪的小范例,有意创建了一个无限循环。 我希望这是一种反模式,但是在我玩它的任何一种方式中,我都发现了一些奇怪的行为。
基本思想是,我们有一个outer
函数,该函数执行不确定的时间,然后调用其回调,以及一个inner
函数,也执行某些不确定的工作(涉及数据库连接),但仅在调用setTimeout(function() {outer(inner)}, someDelay)
。 因此,理想情况下,两个功能都将在15秒内完成,进入睡眠状态,然后唤醒以重新同步环境,但是当然这并不总是这样。
出于某些原因,这似乎是一个不好的设计,这似乎很明显,但是无论如何,我发现以下玩具示例的行为确实令人惊讶。
'use strict';
var outer = function(callback) {
setTimeout(doWork, 1000);
function doWork() {
console.log('Did outer work.');
callback();
}
}
var inner = function() {
setTimeout(function() {outer(inner)}, 2000);
process.stdin.resume();
process.stdin.once('data', function(data) {
console.log('Got ' + data);
process.stdin.pause();
console.log('Did inner work.');
});
}
outer(inner);
当我们运行它时,外部函数每秒执行一次“工作”,从而导致每2秒将额外的outer(inner)
调用添加到堆栈中(我认为)。 但是,对inner()
每次调用都会在等待stdin
阻塞(该想法是模拟数据库锁)。 如果我等到5次调用outer()
,然后再将任何内容发送到stdin
,堆栈中的inner()
的所有5个实例都会处理相同的数据。
我对此感到非常惊讶-我想我只希望有一个函数调用可以接受第一个输入stdin
,而其他四个要阻塞。 同样,这是一个不好的设计,但是-问题:这是否合乎需要? 有人故意使setTimeout()
和JS调用堆栈以这种方式运行,还是其他语言功能的副作用?
还是有道理,而我只是在以错误的方式思考?
在此先谢谢您,对不起,它有点含糊。
我希望这是一种反模式
不,这没有错。 大多数类似服务器的应用程序都是使用无限循环进行编码的-您希望它们永远处理连接(即,直到您杀死它或崩溃)。
导致每2秒将额外的外部(内部)调用添加到堆栈中(我认为)。
否setTimeout
是异步的,这意味着它立即返回,其余代码运行完毕,然后在新的新调用堆栈上执行调度的回调。 堆栈不会以这种半递归模式增长。
但是,对inner()的每次调用都会在等待stdin时阻塞(该想法是模拟数据库锁)。
不, once()
不会阻塞。 它只是安装一个处理程序,该处理程序将在下次事件发生时执行一次。
我真的很惊讶,如果我等到5次调用external()之后再将任何内容发送到stdin,则相同的数据将由堆栈上的inner()的所有5个实例处理。
是的-有5个data
事件处理程序正在等待事件,并且在触发事件时都将使用输入来执行它们。
如果要在继续递归之前阻塞事件直到发生,则需要将setTimeout
安排在data
事件侦听器中,以安排下一次读取:
function inner() {
process.stdin.resume();
process.stdin.once('data', function(data) {
console.log('Got ' + data);
process.stdin.pause();
console.log('Did inner work.');
setTimeout(function() {outer(inner)}, 2000);
});
}
您甚至可能想忽略此处的超时,并调用outer(inner);
直。
这种行为不足为奇:您将多个单独的处理程序连接到stdin
data
事件,因此在事件运行时,它将调用这些处理程序中的每个处理程序。 就像这样:
process.stdin.once('data', function(data) {
console.log("callback1: " + data);
});
process.stdin.once('data', function(data) {
console.log("callback2: " + data);
});
process.stdin.once('data', function(data) {
console.log("callback3: " + data);
});
如果运行该命令,然后键入一些内容并按Enter,则所有三个回调都将接收该事件,因为毕竟所有三个回调都已为该事件注册。
这正是inner
所做的:将data
事件挂接到多个回调中。
这个版本显示挂钩到data
事件的处理程序的数量(以及实际排队的时间),可能会使事情更加清楚:
'use strict';
var handlerCounter = 0;
var outer = function(callback) {
console.log("outer called, queue doWork for 1s from now");
setTimeout(doWork, 1000);
function doWork() {
console.log('doWork called');
callback();
}
}
var inner = function() {
console.log('Queueing outer(inner) call for 2s from now');
setTimeout(outer, 2000, inner);
process.stdin.resume();
++handlerCounter;
console.log('Adding another handler to the `data` event, total will be: ' + handlerCounter);
process.stdin.once('data', function(data) {
--handlerCounter; // Since `once` removed it
console.log('Got ' + data + ', handlers now: ' + handlerCounter);
process.stdin.pause();
});
}
outer(inner);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.