关于javascript:允许在Node.js中每毫秒多次运行setInterval

Allow running setInterval more than once per millisecond in nodejs

我有一个节点脚本,该脚本应该利用单个节点进程可以获得的所有CPU资源。但是我发现setInterval太慢了。

可以肯定的是,我在文档中找到了这一点:

When delay is larger than 2147483647 or less than 1, the delay will be
set to 1.

来源:https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args

现在,我想知道是否有办法进一步减少该限制,或者是否可以使用其他功能。

我不能只使用普通循环,因为还有其他异步事物需要同时运行。

编辑:
再说一遍:我不能只使用普通循环,因为还有其他异步事物需要同时运行。
我不确定为什么这么难理解。

在正常循环运行时,您将阻止其他所有程序的执行。是否将循环放入另一个异步执行的函数中都没关系。

这是什么意思?

让我们看一些例子:

1
2
3
4
5
setInterval(()=>{console.log('a')},1000) // asynchronous thing that needs to run in the background

while (true) {
    // do whatever
}

该代码将做什么?它将阻止所有内容。 console.log('a')将不会连续执行。

1
2
3
4
5
6
setInterval(()=>{console.log('a')},1000) // asynchronous thing that needs to run in the background
setTimeout(()=>{
    while (true) {
        // do whatever
    }
}, 1)

在while循环开始时,这也将阻止间隔的执行。


我相信这个问题属于节点而不是浏览器。您可以使用以下某些选项(递归/循环)来减少延迟时间。

setImmediate

setImmediate - Schedules the"immediate" execution of the callback after I/O events' callbacks. Returns an Immediate for use with clearImmediate().

When multiple calls to setImmediate() are made, the callback functions are queued for execution in the order in which they are created. The entire callback queue is processed every event loop iteration. If an immediate timer is queued from inside an executing callback, that timer will not be triggered until the next event loop iteration.

来自node指南:

setImmediate and setTimeout are similar, but behave in different
ways depending on when they are called.

  • setImmediate() is designed to execute a script once the current poll phase completes.
  • setTimeout() schedules a script to be run after a minimum threshold in ms has elapsed.

process.nextTick

The process.nextTick() method adds the callback to the"next tick
queue". Once the current turn of the event loop turn runs to
completion, all callbacks currently in the next tick queue will be
called.

node指南

We recommend developers use setImmediate() in all cases because it's
easier to reason about (and it leads to code that's compatible with a
wider variety of environments, like browser JS.)


感谢Josh Lin提出的只运行多个时间间隔的想法。最后,我为setIntervalclearInterval使用了两个简单的包装函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
function setInterval2(cb,delay) {
    if (delay >= 1)
        return [setInterval(cb,delay)];
    var intervalArr = [];
    var intervalCount = Math.round(1/delay);
    for (var i=0; i<intervalCount; i++)
        intervalArr.push(setInterval(cb,1));
    return intervalArr
}

function clearInterval2(intervalArr) {
    intervalArr.forEach(clearInterval);
}

它的工作原理与原始功能类似:

1
2
3
4
5
6
7
8
9
var count = 0;

// run interval every 0.01 milliseconds:
var foo = setInterval2(function(){
    count++;
},0.01);

// stop execution:
clearInterval2(foo)


1 setInterval次运行更多!

1
2
3
4
5
6
7
8
9
10
11
let count = 0,
  by = 100,
  _intervals = [],
  timelimit = 100
for (let i = 0; i < by; i++) {
  _intervals[i] = setInterval(() => count++, 1)
}
setTimeout(() => {
  _intervals.forEach(x => clearInterval(x))
  console.log(`count:${count}`)
}, timelimit)

enter image description here

2. setTimeout递归程序运行更少!

1
2
3
4
5
6
7
8
9
10
11
12
let count = 0,
  go = true
recurser()
setTimeout(() => {
  go = false
  console.log(`count:${count}`)
}, 100)

function recurser() {
  count++
  go && setTimeout(recurser)
}

enter image description here

3.requestAnimationFrame运行少!

1
2
3
4
5
6
7
8
9
10
11
12
13
let count = 0,
  go = true,
  timelimit = 100
step()
setTimeout(() => {
  go = false,
    console.log(`count:${count}`)
}, timelimit)

function step() {
  count++
  go && requestAnimationFrame(step)
}

enter image description here

因此,据我所知,多次运行setInterval,我相信while会更多


您问是否有可能

run setInterval more than once per millisecond in nodejs

正如您在问题中所指出的,使用setInterval是不可能的,因为在node.js中始终存在至少1 ms的最小延迟。在浏览器中,通常存在至少10 ms的最小延迟。

但是,您可以通过其他方式实现重复运行CPU密集型代码而不会造成不必要的延迟的目标。

如《 The Reason》的答案所述,setImmediate是node.js中提供的一个不错的选择。由于setImmediate的浏览器支持有限,并且将来不太可能得到广泛支持,因此还有另一种方法也可以在浏览器中使用。

当浏览器为setIntervalsetTimeout强制执行最小延迟时,setTimeout的延迟从设置计时器开始而不是从运行计时器开始执行。如果我们反复使用setTimeout来调用CPU密集型代码,则可以确保始终将定时器设置为提前1015 ms(如果代码至少需要1015 ms才能运行),从而将实际延迟减少为0 ms。

下面的演示代码片段从此答案中借用了代码,以演示如何使用预先设置的计时器使延迟小于强制执行的延迟。在我测试的浏览器中,这通常会导致0毫秒的延迟。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// First: repeat runCPUForAtLeast50ms() 10 times
// with standard repeated setTimeouts and enforced delays
testTimeout(10, function(){
  // Then: repeat runCPUForAtLeast50ms() 10 times
  // using a repeated set of queued setTimeouts
  // circumventing the enforced delays
  testTimeout(10, false, true);
});

function testTimeout(repetitions, next, multiple) {
  var delays = [];
  var lastCheck;
  var extraTimers;

  function runner() {
    if(lastCheck){
      delays.push((+new Date) - lastCheck);
    }
    if(repetitions > 0) {
      //process chunk
      runCPUForAtLeast50ms();
      //set new timer
      setTimeout(runner);
    } else if(repetitions === 0) {
      //report result to console
      console.log((multiple?
        'Repeated multiple timers delays: ' :
        'Repeated single timer delays: ') + delays.join(', '));
      //invoke next() function if provided
      next && next();
    }
    repetitions--;
    lastCheck = +new Date;
  }

  setTimeout(runner);

  if(multiple){
   // make sure that there are always a fixed
   // number of timers queued by setting extra timers
   // at start
   extraTimers = 10;
   while(extraTimers--)
     setTimeout(runner);
  }
}

function runCPUForAtLeast50ms() {
  var d = (+new Date) + 50;
  while(+new Date < d);
}


简而言之,你不能。由于Javascript / Node是单线程应用程序,因此存在一些限制。这就是为什么您有异步中断。

长答案:
从计算机体系结构的角度来看,现代CPU和内核调度不是确定性的。如果您想要这种精细的控制,我建议您看一下没有内核调度程序的MCU和嵌入式解决方案。因为,您的OS还有许多其他进程以及占用CPU时间的内核进程,所以内核调度程序必须不断调度要在CPU上运行的不同进程并满足许多不同的需求。

即使设置为1毫秒,当您尝试测量时,它也可能不是1毫秒(确切的时间取决于操作系统,硬件和计算机上运行的进程数量)。

现在,如果要使用所有CPU资源,则不可能。

但是,如果您想尽可能多地利用资源,则可以探索当前的编程模式。例如,您可以调度一百万个线程(您的计算机可能无法处理),或疯狂的大量进程,并让调度程序将进程不断地放入CPU,因此没有空闲时间,并且最高CPU使用率。

另外,您可以运行CPU压力测试,这些测试旨在简单地使CPU最大化并保持其高温燃烧-确保已安装冷却解决方案。


我认为您可以使用异步模块解决您的问题...一种方法可能是:

1
2
3
4
5
6
7
8
9
10
async.parallel([
  (callback) => {
    // do normal stuff
  },
  (callback) => {
    // do your loop
  }
], (err, results) => {
  // ...
});

但是请考虑官方文档中的此说明...

Note: parallel is about kicking-off I/O tasks in parallel, not about
parallel execution of code. If your tasks do not use any timers or
perform any I/O, they will actually be executed in series. Any
synchronous setup sections for each task will happen one after the
other. JavaScript remains single-threaded.