zoukankan      html  css  js  c++  java
  • nodejs 异步之 Timer &Tick; 篇

    nodejs 异步之 Timer &Tick; 篇 - CNode

    nodejs 异步之 Timer &Tick; 篇

    Timer:
     

    在前端开发中,我们 经常会使用setTimeout 函数组,这组函数其实不属于语言标准,他们只是extentsion ,在浏览器中,他们属于 BOM(浏览器对象扩展),即它的确切定义为:window.setTimeout ,和window.alert , window.open 等函数处于同一层次。



    你可以在浏览器的控制台监测window 对象 ,或查看chromium 的实现 :

    http://codesearch.google.com/#OAMlx_jo-ck/src/third_party/WebKit/Source/WebCore/page/DOMWindow.h



    在nodejs ,当然也要实现自己的Timer ,在nodejs 的src 目录下,timer.h/cc 是对 libev 中的 ev_timer 进行了一层浅包装,在src/node.js 文件中,把这组函数放置于全局范围中(相当于浏览器中window)

    startup.globalTimeouts = function() {
    
      global.setTimeout = function() {
    
        var t = NativeModule.require('timers');
    
        return t.setTimeout.apply(this, arguments);
    
      };
    
      global.setInterval = function() {
    
        var t = NativeModule.require('timers');
    
        return t.setInterval.apply(this, arguments);
    
      };
    
    ...


    通过我们对libev的分析,我们已经知道,在libev中队timer的 添加/删除/更新 操作都是 O(lg(n)) ,

    (libev 分析 http://cnodejs.org/blog/?p=2489



    但其实在我们的日常开发中,以网络套接字为例,我们往往会对其设置timeout,即如果一段时间(120s 或 60s)此套接字没有活动事件发生,我们就关闭它(这种技巧非常重要,否则由于一些内部或外部的原因,很容易出现描述符占用过多的现象),注意到,我们的超时时间是一个统一的值,这时候,我们可以采用如下算法:(lib/_linklist.js 真实实现)







    当我们在不同的时刻多次 调用 setTimeout(fn ,1000) 时,我们把所有的这些事件仅由一个timer 来管理, 这些事件被放到一个双向链表中,其顺序自然而然的就是按时间来排序的,当某一时刻,timer 触发时,会依次的从prev链表头中取出节点,进行检查, 如果超时则触发...

    (参见lib/timer_legacy.js 由于libev无法在windows上 很好的工作,所以nodejs项目组又对libev,libeio 作了一层封装 ,称为libuv, 使用了传统的方法文件以_legacy 结尾,使用了libuv 的以_uv 结尾,如net_legacy.js ,net_uv.js ,timers_legacy.js  ,timers_uv.js 等,)



    当我们需要更改某异步事件时,比如,我们添加超时控制的socket上发生了read/ write 事件,这时候,我们需要重新reset 计时,我们仅需要把这个事件从链表中转移到链表尾部即可

    (参见 lib/net_legacy.js)



    删除时的情况与此类似,所以在此场景下,仅耗费一个ev_timer ,而我们的所有异步事件的添加/删除/更新 都是 O(1) 复杂度.



    注意,在lib/timer_*.js  中,当超时值<= 0 时是不做优化的,

    exports.exports.setTimeout = function(callback, after) {
    
      if (after <= 0) {
    
        // Use the slow case for after == 0
    
       timer = new Timer();
    
       timer.ontimeout = callback;
    
      else{
    
        ...
    
      }


    同时setInterval 函数也是没做优化的.



    nextTick :



    nextTick是个很有意思的东西,它其实是一个prepare watcher 来实现的,在libev中的event loop 的每次迭代,在nodejs 中就叫做 “Tick” ,而在libev 中,支持prepare watcher ,它在每次迭代开始时触发  ,在src/node.cc 中 我们看到类似代码(v4.9为例):

    ev_prepare_init(&node::prepare_tick_watcher, node::PrepareTick);
    
    ev_prepare_start(EV_DEFAULT_UC_ &node::prepare_tick_watcher);
    
    ev_unref(EV_DEFAULT_UC);


    在函数 PrepareTick 中 会调用一个回调函数,此函数 _tickCallback 在 src/node.js 中:

    startup.processNextTick =function() {
    
      var nextTickQueue = [];
    
      process._tickCallback = function() {
    
      ...
    
      }
    
      process.nextTick = function(callback) {
    
        nextTickQueue.push(callback);
    
        process._needTickCallback();
    
       };
    
    };


    当我们调用API  process.nextTick 时,其实就是向nextTickQueue  这个闭包队列中添加一个函数,当下次事件循环开始时,会自动触发prepare_tick_watcher ,调用我们设定的函数。

    注意,上面有个问题,那就是如果没有机会进行下次事件循环,比如,此时没有实质性的watcher可供监测了,这时事件循环就会退出 ,为了避免着这种情况, nextTick  函数中会调用一次 process._needTickCallback() ,这个函数会使tick_spinner (一个idle watcher)处于活跃状态 , 来防止事件循环的退出。



    由nextTick 原理我们可知,当我们添加一个待处理事件时,其复杂度为O(1) ,但如果你用setTimeout(fn ,0) 的话,如我们在libev分析中所讲,其一进一出均为 O(lg(n)) , 所以nodejs官方文档讲 :


    On the next loop around the event loop call this callback. This is not a simple alias to setTimeout(fn, 0), it's much more efficient.


    nextTick 确实高效的多,诚不我欺也!



    小节:

    通过以上分析 ,我们至少得出2条粗浅的结论:

    #1  如果可能的话,调用setTimeout时,尽量使用相同的超时值

    #2  尽量用process.nextTick 来代替 setTimeout(fn ,0)



    标签:


    原创文章


    windyrobin 在 2011-9-13 21:45发布


    windyrobin 在 2012-1-19 11:55重新编辑

     
    分享到 weibo

    2 回复

    #1
    snoopy

    最后小结收益良多啊,希望爱多兄能有更多深入浅出的文章~

    继续关注中~


    snoopy 在 2011-9-14 22:15回复


     

    #2
    suqian

    timer复用。。。解除疑惑。。。以后得多深入源码。。


    suqian 在 2011-9-23 00:21回复

  • 相关阅读:
    Infopath Notify 弹出提示信息
    window.showModalDialog 返回值
    【转】获得正文内容中的所有img标签的图片路径
    Json Datable Convert
    Sharepoint 列表 附件 小功能
    Surgey 权限更改
    SQL 触发器用于IP记录转换
    Caml语句 查询分配给当前用户及当前组
    jquery 1.3.2 auto referenced when new web application in VSTS2010(DEV10)
    TFS diff/merge configuration
  • 原文地址:https://www.cnblogs.com/lexus/p/2478282.html
Copyright © 2011-2022 走看看