在Node.js中如何实现setTimeout函数

36

我想知道有没有人知道在node.js中如何实现 setTimeout。我相信我曾经在某个地方读到过,这不是V8的一部分。我试图快速找到实现方式,但在源码(BIG)中找不到它。例如,我发现了 timers.js 这个文件,然后又链接到 timer_wrap.cc。但这些文件不能完全回答我的问题。

  • V8是否有 setTimeout 实现?从源代码来看,我猜答案是否定的。
  • setTimeout 是如何实现的?JavaScript还是本地代码或两者的组合?从 timers.js 文件中,我认为是两者结合:

  • var Timer = process.binding('timer_wrap').Timer;`
    
  • 当添加多个计时器(setTimeout)时,Node.js如何知道先执行哪一个?它会将所有计时器添加到一个集合中并排序吗?如果是排序的,那么查找需要执行的超时时间是O(1),插入是O(log n)吗?但是我在timers.js中看到他们使用了一个链表?

  • 但是,添加许多计时器根本不是问题吗?
  • 执行这段脚本时:

    var x = new Array(1000),
        len = x.length;
    
    /**
     * Returns a random integer between min and max
     * Using Math.round() will give you a non-uniform distribution!
     */
    function getRandomInt (min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    
    var y = 0;
    
    for (var i = 0; i < len; i++) {
        var randomTimeout = getRandomInt(1000, 10000);
    
        console.log(i + ', ' + randomTimeout + ', ' + ++y);
        setTimeout(function () {
            console.log(arguments);
        }, randomTimeout, randomTimeout, y);
    }
    

    你的CPU使用率会稍微增加一点,但不会太多吗?

  • 我在想,如果我按照顺序一个一个实现所有这些回调函数,我是否会获得更好的性能?
2个回答

27

你已经完成了大部分工作。V8并没有提供setTimeout的实现,因为它不是ECMAScript的一部分。你使用的函数在timers.js中实现,它创建了一个Timeout对象的实例,这个对象是一个包装C类的封装。

源代码中有一条注释描述了他们如何管理定时器。

// Because often many sockets will have the same idle timeout we will not
// use one timeout watcher per item. It is too much overhead.  Instead
// we'll use a single watcher for all sockets with the same timeout value
// and a linked list. This technique is described in the libev manual:
// http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#Be_smart_about_timeouts

这表明它正在使用双向链表,这是链接文章中的第4种方法。

  

如果不是一个请求,而是成千上万个请求(甚至百万级别),都使用相同的超时时间,那么可以做得更好:

     

在启动超时时,计算超时值并将超时放置在列表末尾。

     

然后使用 ev_timer 当超时开始时,按预期在列表开头触发超时(例如使用技巧#3)。

     

当有活动时,从列表中删除定时器,重新计算超时时间,将其再次附加到列表末尾,并确保在从列表开头获取时更新 ev_timer。

     

通过这种方式,可以在 O(1)时间内管理无限数量的超时,以开始、停止和更新定时器,代价是一个主要的复杂性,需要使用恒定的超时时间。 恒定的超时时间确保列表保持排序。

Node.js 是围绕异步操作设计的,setTimeout 是其中重要的一部分。我不会试图变得聪明,只需使用他们提供的内容。相信它足够快,直到你证明在你特定的情况下它是个瓶颈。不要陷入过早优化的困境。

更新

实际上,你有一个超时字典,所以所有100ms的超时都被分组在一起。每当添加新的超时或最旧的超时触发时,它都会被追加到列表中。这意味着最旧的超时,即将最先触发的超时,在列表开头。这个列表只有一个计时器,并且它是根据第一个即将过期的项目的时间设置的。

如果您调用 setTimeout 1000 次,并且每次超时值相同,则它们将按您调用 setTimeout 的顺序附加到列表中,不需要排序。 这是一个非常有效的设置。


在这个例子中,超时时间总是相同的吗?60秒? - Alfred
所以你想在60秒内完成许多事情,而且你正在尝试弄清楚是将所有这些事情合并在一起还是为每个事情单独创建一个setTimeout? - Timothy Strimple
那个你链接的文档有60秒的延迟。我的延迟可以是任何值,而且可能很大! - Alfred
这些只是例子。它适用于任何大于0且小于最大值的超时时间。 - Timothy Strimple
但我不明白你如何能够有效地超时。由于示例中的超时不断发生,列表保持排序状态。请参见您引用的最后一行。 - Alfred
我更新了我的回答,并提供了有关计时器设置的更多细节。希望这可以帮助到您。 - Timothy Strimple

7
没有多个定时器的问题!当uv循环调用轮询时,它会将所有定时器中最接近超时时间的定时器作为timeout参数传递给轮询函数。
“所有定时器中最接近超时时间的定时器”可以在以下链接中找到:https://github.com/joyent/node/blob/master/deps/uv/src/unix/timer.c #120
RB_MIN(uv__timers, &loop->timer_handles)  

将超时参数传递给poll API
请参考以下链接:https://github.com/joyent/node/blob/master/deps/uv/src/unix/core.c #276
timeout = 0;  
if ((mode & UV_RUN_NOWAIT) == 0)  
    timeout = uv_backend_timeout(loop);  

uv__io_poll(loop, timeout); 

注意:在Windows操作系统上,逻辑几乎相同。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接