Javascript node.js:setInterval() 跳过调用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12433296/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
node.js: setInterval() skipping calls
提问by alexfernandez
For an upcoming project with node.js I need to perform various housekeeping tasks at periodic times. Specifically some tasks every millisecond, others every 20 ms (50 times per second) and still others every second. So I thought about using setInterval(), with funny results: many function calls were being skipped.
对于即将到来的 node.js 项目,我需要定期执行各种内务管理任务。具体来说,有些任务每毫秒执行一次,有些每 20 毫秒执行一次(每秒 50 次),还有一些每秒执行一次。所以我考虑使用 setInterval(),结果很有趣:很多函数调用都被跳过了。
The benchmark I used is as follows:
我使用的基准如下:
var counter = 0;
var seconds = 0;
var short = 1;
setInterval(function() {
counter ++;
}, short);
setInterval(function() {
seconds ++;
log('Seconds: ' + seconds + ', counter: ' +
counter + ', missed ' +
(seconds * 1000 / short - counter));
}, 1000);
There is a long timer of one second and a short one that can be adjusted using the variable short
, in this case 1 ms. Every second we print the difference between the number of expected ticks in the short cycle and the actual number of times the short counter was updated.
有一个一秒的长计时器和一个可以使用变量调整的短计时器short
,在本例中为 1 毫秒。我们每一秒都打印短周期中预期的滴答数与短计数器更新的实际次数之间的差值。
Here is how it behaves when the short timer is 1 ms:
以下是短定时器为 1 ms 时的行为:
2012-09-14T23:03:32.780Z Seconds: 1, counter: 869, missed 131
2012-09-14T23:03:33.780Z Seconds: 2, counter: 1803, missed 197
2012-09-14T23:03:34.781Z Seconds: 3, counter: 2736, missed 264
...
2012-09-14T23:03:41.783Z Seconds: 10, counter: 9267, missed 733
Many function calls are skipped. Here it is for 10 ms:
许多函数调用被跳过。这是 10 毫秒:
2012-09-14T23:01:56.363Z Seconds: 1, counter: 93, missed 7
2012-09-14T23:01:57.363Z Seconds: 2, counter: 192, missed 8
2012-09-14T23:01:58.364Z Seconds: 3, counter: 291, missed 9
...
2012-09-14T23:02:05.364Z Seconds: 10, counter: 986, missed 14
Better, but roughly one function call is skipped every second. And for 20 ms:
更好,但大约每秒跳过一个函数调用。对于 20 毫秒:
2012-09-14T23:07:18.713Z Seconds: 1, counter: 46, missed 4
2012-09-14T23:07:19.713Z Seconds: 2, counter: 96, missed 4
2012-09-14T23:07:20.712Z Seconds: 3, counter: 146, missed 4
...
2012-09-14T23:07:27.714Z Seconds: 10, counter: 495, missed 5
Finally for 100 ms:
最后 100 毫秒:
2012-09-14T23:04:25.804Z Seconds: 1, counter: 9, missed 1
2012-09-14T23:04:26.803Z Seconds: 2, counter: 19, missed 1
2012-09-14T23:04:27.804Z Seconds: 3, counter: 29, missed 1
...
2012-09-14T23:04:34.805Z Seconds: 10, counter: 99, missed 1
In this case it skips very few calls (the gap increased to 2 after 33 seconds and to 3 after 108 seconds.
在这种情况下,它跳过很少的调用(间隔在 33 秒后增加到 2,在 108 秒后增加到 3。
The numbers vary, but are surprisingly consistent between runs: Running the first 1 ms benchmark three times yielded a delay after 10 seconds of 9267, 9259 and 9253.
数字有所不同,但在运行之间惊人地一致:运行第一个 1 毫秒基准测试 3 次,在 9267、9259 和 9253 10 秒后产生延迟。
I have found no references for this particular problem. There is this much cited Ressig postand lots of related JavaScript questions, but most assume that the code runs in a browser and not in node.js.
我没有找到这个特定问题的参考。有很多引用的 Ressig 帖子和许多相关的 JavaScript 问题,但大多数假设代码在浏览器中运行,而不是在 node.js 中运行。
Now for the dreaded question: what is going on here? Just joking; obviously function calls are being skipped. But I fail to see the pattern. I thought that the long cycles might be preventing the short ones, but it doesn't make any sense in the 1 ms case. Short cycle function calls are not overlapping since they just update a variable, and the node.js process is near 5% CPU even with a short cycle of 1 ms. Load average is high though, at about 0.50. I don't know why a thousand calls are stressing my system so much, though, since node.js handles many more clients perfectly; it must be true that setInterval() is CPU intensive(or I am doing something wrong).
现在问一个可怕的问题:这里发生了什么?开个玩笑;显然函数调用被跳过。但我看不到模式。我认为长周期可能会阻止短周期,但在 1 毫秒的情况下没有任何意义。短周期函数调用不会重叠,因为它们只是更新一个变量,即使是 1 毫秒的短周期,node.js 进程也接近 5% 的 CPU。不过,平均负载很高,约为 0.50。我不知道为什么一千个调用对我的系统造成如此大的压力,因为 node.js完美地处理了更多的客户端;它必须是真实的的setInterval()是CPU密集型(或我做错了什么)。
An obvious solution is to group function calls using longer timers, and then run short cycle function calls many times to simulate a shorter timer. Then use the long cycle as a "broom wagon" that makes any calls missed in the lower intervals. An example: set up 20 ms and 1000 ms setInterval() calls. For 1 ms calls: call them 20 times in the 20 ms callback. For the 1000 ms call: check how many times the 20ms function has been called (e.g. 47), do any remaining calls (e.g. 3). But this scheme is going to be a bit complex, since calls may overlap in interesting ways; also it will not be regular although it may look like it.
一个明显的解决方案是使用更长的计时器对函数调用进行分组,然后多次运行短周期函数调用来模拟更短的计时器。然后将长周期用作“扫帚马车”,使任何在较低间隔中错过的电话。示例:设置 20 毫秒和 1000 毫秒的 setInterval() 调用。对于 1 ms 调用:在 20 ms 回调中调用它们 20 次。对于 1000 ms 调用:检查 20ms 函数被调用了多少次(例如 47),执行任何剩余的调用(例如 3)。但是这个方案会有点复杂,因为调用可能会以有趣的方式重叠;虽然它可能看起来像它,但它也不会是常规的。
The real question is: can it be done better, either with setInterval() or other timers within node.js? Thanks in advance.
真正的问题是:使用 setInterval() 或 node.js 中的其他计时器可以做得更好吗?提前致谢。
采纳答案by zer02
SetInterval functions in javascript are not accurate. You should try to use a high resolution timer.Building accurate Timers in javascript
JavaScript 中的 SetInterval 函数不准确。您应该尝试使用高分辨率计时器。在 javascript 中构建准确的计时器
回答by Vadim Baryshev
Look at this doc: http://nodejs.org/api/timers.html#timers_settimeout_callback_delay_arg
看看这个文档:http: //nodejs.org/api/timers.html#timers_settimeout_callback_delay_arg
It is important to note that your callback will probably not be called in exactly delay milliseconds - Node.js makes no guarantees about the exact timing of when the callback will fire, nor of the ordering things will fire in. The callback will be called as close as possible to the time specified.
重要的是要注意,您的回调可能不会在确切的延迟毫秒内被调用 - Node.js 不保证回调将触发的确切时间,也不保证将触发的顺序。回调将被调用为尽可能接近指定的时间。
This happens because application code blocks the event loop. All timers and I/O events can be handled only on the nextTick
.
这是因为应用程序代码阻塞了事件循环。所有定时器和 I/O 事件只能在nextTick
.
You can see this behaviour with this code:
您可以使用以下代码查看此行为:
setInterval(function() {
console.log(Date.now());
for (var i = 0; i < 100000000; i++) {
}
}, 1);
Try to change iterations count and see results.
尝试更改迭代次数并查看结果。
Ideally, the timer will be triggered exactly if the applications tick will last less than one ms. But this is not practicable in a real application.
理想情况下,如果应用程序滴答持续时间少于 1 毫秒,则将准确触发计时器。但这在实际应用中是不可行的。
回答by alexfernandez
The answer happens to be a combination of those given by Vadim and zer02, so I am leaving a write-up here. As Vadim said, the system cannot cope with too frequent updates, and adding some load to the system is not going to help. Or rather the runtime cannot cope; the system should be more than capable of firing the callback every millisecond if needed, but for some unexplained reason often it doesn't want to.
答案恰好是 Vadim 和 zer02 给出的答案的组合,所以我在这里留下了一篇文章。正如 Vadim 所说,系统无法应对过于频繁的更新,给系统增加一些负载也无济于事。或者更确切地说,运行时无法应对;如果需要,系统应该能够每毫秒触发一次回调,但由于某些无法解释的原因,它通常不想要。
The solution is to use accurate timers, as zer02 commented. Do not be misled by the name; the mechanism used is the same setTimeout(), but the delay is adjusted depending on the time left until the timer should fire. So, if the time is over then the "accurate timer" will call setTimeout(callback, 0) which is run immediately. System load is, surprisingly, less than with setInterval(): about 2% of the CPU instead of 5%, in my very unscientific sample.
解决方案是使用准确的计时器,正如 zer02 评论的那样。不要被名字误导;使用的机制与 setTimeout() 相同,但延迟会根据计时器触发之前的剩余时间进行调整。因此,如果时间结束,那么“准确计时器”将调用立即运行的 setTimeout(callback, 0)。令人惊讶的是,系统负载比 setInterval() 少:在我非常不科学的样本中,大约是 CPU 的 2% 而不是 5%。
This simple function may come in handy:
这个简单的函数可能会派上用场:
/**
* A high resolution timer.
*/
function timer(delay, callback)
{
// self-reference
var self = this;
// attributes
var counter = 0;
self.running = true;
var start = new Date().getTime();
/**
* Delayed running of the callback.
*/
function delayed()
{
callback(delay);
counter ++;
var diff = (new Date().getTime() - start) - counter * delay;
if (!self.running) return;
setTimeout(delayed, delay - diff);
}
// start timer
delayed();
setTimeout(delayed, delay);
}
To use, just call new timer(delay, callback);
. (Yes, I reversed the order of the parameters since having the callback first is very annoying.) To stop it, set timer.running = false
.
要使用,只需调用new timer(delay, callback);
. (是的,我颠倒了参数的顺序,因为首先进行回调非常烦人。)要停止它,请设置timer.running = false
.
One final note: setTimeout(callback, delay) does not use recursion as I feared (as in: wait for some time, then invoke the callback), it just places the callback in a queue which will be called by the runtime when its turn comes, in the global context.
最后一个注意事项:setTimeout(callback, delay) 没有像我担心的那样使用递归(例如:等待一段时间,然后调用回调),它只是将回调放在一个队列中,该队列将在轮到运行时调用来,在全球范围内。