zoukankan      html  css  js  c++  java
  • Libev源码分析04:Libev中的相对时间定时器

             Libev中的超时监视器ev_timer,就是简单的相对时间定时器,它会在给定的时间点触发超时事件,还可以在固定的时间间隔之后再次触发超时事件。

             所谓的相对时间,指的是如果你注册了一个1小时的超时事件,然后调整系统时间到了去年的一月份,该超时事件依然会在1个小时之后触发。

     

    一:数据结构

    1:超时监视器ev_timer结构:

    typedef struct ev_timer
    {
        int active; 
        int pending;
        int priority;
        void *data;
        void (*cb)(struct ev_loop *loop, struct ev_timer *w, int revents);  
    
        ev_tstamp at;
        ev_tstamp repeat; /* rw */
    } ev_timer;

             其中的前五个成员是监视器的公共成员,其中的active在超时监视器中有特殊作用,那就是标明该监视器在堆数组timers中的下标。后两个成员at和repeat是ev_timer特有的。at表明定时器第一次触发的时间点,该是是根据mn_now设置的,repeat必须大于等于0,它表示每隔repeat秒,该定时器再次触发。如果repeat为0,表明该定时器只触发一次。

     

    2:ev_watcher_time结构

    typedef struct ev_watcher_time
    {
        int active; 
        int pending;
        int priority;
        void *data;
        void (*cb)(struct ev_loop *loop, struct ev_watcher_time *w, int revents);  
    
        ev_tstamp at;
    } ev_watcher_time;
    
    typedef ev_watcher_time *WT;
    

            ev_watcher_time的结构与ev_timer几乎一样,只是少了最后一个成员。该结构其实是ev_timer和ev_periodic的父类,它包含了ev_timer和ev_periodic的共有成员。

     

    3:堆元素ANHE

    #if EV_HEAP_CACHE_AT
        typedef struct 
        {
            ev_tstamp at;
            WT w;
        } ANHE;
    #else  
    typedef WT ANHE;
    #endif
    

             宏EV_HEAP_CACHE_AT的作用,是为了提高在堆中的缓存利用率,如果没有定义该宏,堆元素就是指向ev_watcher_time结构的指针。如果定义了该宏,则还将堆元素的关键成员at进行缓存。

     

    二:超时监视器函数

    1:设置超时监视器ev_timer_set

    #define ev_timer_set(ev, after_, repeat_)      do {
        ((ev_watcher_time *)(ev))->at = (after_); 
        (ev)->repeat = (repeat_); 
    } while (0)
    

    2:启动超时监视器ev_timer_start

    #if EV_HEAP_CACHE_AT
      #define ANHE_w(he)        (he).w
      #define ANHE_at(he)       (he).at
      #define ANHE_at_cache(he) (he).at = (he).w->at
    #else  
      #define ANHE_w(he)        (he)
      #define ANHE_at(he)       (he)->at
      #define ANHE_at_cache(he)
    #endif
    
    void ev_timer_start (struct ev_loop *loop, ev_timer *w)
    {
        if (expect_false (ev_is_active (w)))
            return;
    
        ev_at (w) += mn_now;
    
        assert (("libev: ev_timer_start called with negative timer repeat value", w->repeat >= 0.));
    
        ++timercnt;
        ev_start (EV_A_ (W)w, timercnt + HEAP0 - 1);
        array_needsize (ANHE, timers, timermax, ev_active (w) + 1, EMPTY2);
        ANHE_w (timers [ev_active (w)]) = (WT)w;
        ANHE_at_cache (timers [ev_active (w)]);
        upheap (timers, ev_active (w));
    }

             代码比较简单,首先设置监视器的at成员,表明在at时间点,超时事件会触发,注意at是根据mn_now设置的,也就是相对于系统启动时间而言的(或者是日历时间)。之后,就是将该监视器加入到堆timer中,首先将该监视器加到堆中的最后一个元素,然后调用upheap调整堆。注意监视器的active成员,表明该监视器在堆数组中的下标。

     

    3:停止超时监视器ev_timer_stop

    void ev_timer_stop (EV_P_ ev_timer *w) EV_THROW
    {
        clear_pending (EV_A_ (W)w);
        if (expect_false (!ev_is_active (w)))
            return;
    
        int active = ev_active (w);
    
        --timercnt;
    
        if (expect_true (active < timercnt + HEAP0))
        {
            timers [active] = timers [timercnt + HEAP0];
            adjustheap (timers, timercnt, active);
        }
    
        ev_at (w) -= mn_now;
    
        ev_stop (EV_A_ (W)w);
    }

             首先调用clear_pending,如果该监视器已经处于pending状态,将其从pendings中删除。然后根据监视器中的active成员,得到其在timers堆上的索引,将该监视器从堆timers上删除,重新调整堆结构。然后调用ev_stop停止该监视器。

     

    4:timers_reschedule更新定时器的时间

             在ev_run中,每次loop中,在调用backend_poll前后都会调用time_update更新当前时间,如果发现时间被人调整,则需要更新定时器,更新相对定时器时,调用timers_reschedule(loop, ev_rt_now - mn_now),其中,ev_rt_now是最新的当前日历时间,mn_now是之前记录的日历时间,他们之间的差值就表示时间调整了多少,timers_reschedule代码如下:

    static void timers_reschedule (struct ev_loop *loop, ev_tstamp adjust)
    {
        int i;
    
        for (i = 0; i < timercnt; ++i)
        {
            ANHE *he = timers + i + HEAP0;
            ANHE_w (*he)->at += adjust;
            ANHE_at_cache (*he);
        }
    }
    
            代码较简单,就是更新堆timers中的每个元素的at值。

    5:timers_reify将激活的超时事件排队

            每次调用backend_poll之前,都会根据ANHE_at (timers [HEAP0]) - mn_now的值,校准backend_poll的阻塞时间waittime,这样就能尽可能的保证定时器能够按时触发。

            调用backend_poll之后,就会调用timers_reify查看timers中哪些定时器触发了,代码如下:

    void timers_reify(struct ev_loop *loop)
    {
        if (timercnt && ANHE_at (timers [HEAP0]) < mn_now)
        {
            do
            {
                ev_timer *w = (ev_timer *)ANHE_w (timers [HEAP0]);
    
                /* first reschedule or stop timer */
                if (w->repeat)
                {
                    ev_at (w) += w->repeat;
                    if (ev_at (w) < mn_now)
                        ev_at (w) = mn_now;
    
                    ANHE_at_cache (timers [HEAP0]);
                    downheap (timers, timercnt, HEAP0);
                }
                else
                    ev_timer_stop (EV_A_ w); /* nonrepeating: stop timer */
    
                feed_reverse (EV_A_ (W)w);
            }
            while (timercnt && ANHE_at (timers [HEAP0]) < mn_now);
    
            feed_reverse_done (loop, EV_TIMER);
        }
    }
    void feed_reverse (struct ev_loop *loop, W w)
    {
        array_needsize (W, rfeeds, rfeedmax, rfeedcnt + 1, EMPTY2);
        rfeeds [rfeedcnt++] = w;
    }
    
    void feed_reverse_done (struct ev_loop *loop, int revents)
    {
        do
            ev_feed_event (EV_A_ rfeeds [--rfeedcnt], revents);
        while (rfeedcnt);
    }
    
    

            如果堆顶元素的超时时间点at小于mn_now(是小于,而不是等于,原因见:http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#The_special_problem_of_being_too_ear),说明堆顶元素的超时时间到时,进入循环,取出堆顶元素的监视器,如果w->repeat大于0,则设置该超时监视器下一次触发的时间点,然后调用downheap调整堆结构。否则,直接调用ev_timer_stop停止该监视器。最后,调用feed_reverse将该监视器放入rfeeds数组中,该数组对当前已经触发的超时监视器缓存。然后,继续查看新的堆顶元素是否已经超时,超时的话接着上面的步骤进行处理。

             将所有本次触发的超时事件都处理完之后,调用feed_reverse_done,将缓存数组rfeeds中每个元素通过ev_feed_event添加到pendings数组中。

     

    三:例子

    void timer_action(struct ev_loop *main_loop,ev_timer *timer_w,int e)
    {
        time_t now;
        now = time(NULL);
        printf("in tiemr cb%d , cur time is %s
    ", (int)(timer_w->data), ctime(&now));
    }
    
    int main(int argc ,char *argv[])
    {
        struct ev_loop *main_loop = ev_default_loop(0);
    
        timer_w1.data = (void *)1;
        ev_init(&timer_w1,timer_action);
        ev_timer_set(&timer_w1,10,5);     
        ev_timer_start(main_loop,&timer_w1);
    
    
        timer_w2.data = (void *)2;
        ev_init(&timer_w2,timer_action);
        ev_timer_set(&timer_w2,5,10);     
        ev_timer_start(main_loop,&timer_w2);
    
        time_t now;
        now = time(NULL);
        printf("begin time time is %s
    ", ctime(&now));
    
        ev_run(main_loop,0);
        return;
    }
    

             结果打印:

    begin time time is Wed Oct 21 21:55:31 2015

    in tiemr cb2 , cur time is Wed Oct 21 21:55:36 2015

    in tiemrcb1 , cur time is Wed Oct 21 21:55:41 2015

    in tiemrcb1 , cur time is Wed Oct 21 21:55:46 2015

    in tiemr cb2 , cur time is Wed Oct 21 21:55:46 2015

    in tiemrcb1 , cur time is Wed Oct 21 21:55:51 2015

    in tiemrcb1 , cur time is Wed Oct 21 21:55:56 2015

    in tiemr cb2 , cur time is Wed Oct 21 21:55:56 2015

    in tiemrcb1 , cur time is Wed Oct 21 21:56:01 2015

    ...

  • 相关阅读:
    java匿名内部类
    【绝对给力】Android开发免豆资料(教程+工具+源码)地址汇总
    【绝对给力】Android开发免豆资料(教程+工具+源码)地址汇总
    【分享】Java学习之路:不走弯路,就是捷径
    【分享】熟练的Java程序员应该掌握哪些技术?
    【分享】熟练的Java程序员应该掌握哪些技术?
    android 小知识点
    android 小知识点
    Eclipse快捷键大全
    Eclipse快捷键大全
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247102.html
Copyright © 2011-2022 走看看