• Libev源码分析05:Libev中的绝对时间定时器


            Libev中的超时监视器ev_periodic,是绝对时间定时器,不同于ev_timer,它是基于日历时间的。比如如果指定一个ev_periodic在10秒之后触发(ev_now() + 10),然后将系统时间调整为去年的一月一号,则该定时器会在一年后才触发超时事件。(ev_timer依然会在10秒之后触发)

     

    一:数据结构

             超时监视器ev_ periodic结构:

    typedef struct ev_periodic
    {
        int active; 
        int pending;
        int priority;
        void *data;
        void (*cb)(struct ev_loop *loop, struct ev_periodic *w, int revents);  
        ev_tstamp at;
    
        ev_tstamp offset; /* rw */
        ev_tstamp interval; /* rw */
        ev_tstamp (*reschedule_cb)(struct ev_periodic *w, ev_tstamp now) EV_THROW; /* rw */
    } ev_periodic;

             可见其中的前六个成员与ev_timer和ev_watcher_time是一样的。与ev_timer类似,ev_periodic中的active也标明该监视器在堆数组periodics中的下标;at表明超时事件触发的时间点,共有三种设置方法,而且offset、interval和reschedule_cb都是用来设置触发时间的,这个会在下面说明。

     

    二:监视器函数

    1:设置超时监视器

    #define ev_periodic_set(ev,ofs_,ival_,rcb_)  do {
      (ev)->offset = (ofs_); 
      (ev)->interval = (ival_); 
      (ev)->reschedule_cb = (rcb_); 
     }while (0)
    
    #define ev_periodic_init(ev,cb,ofs,ival,rcb) do {
       ev_init ((ev), (cb)); 
       ev_periodic_set ((ev),(ofs),(ival),(rcb)); 
     } while (0)
    

    2:启动监视器ev_periodic_start

    void ev_periodic_start (struct ev_loop *loop, ev_periodic *w)
    {
        if (expect_false (ev_is_active (w)))
            return;
    
        if (w->reschedule_cb)
            ev_at (w) = w->reschedule_cb (w, ev_rt_now);
        else if (w->interval)
        {
            assert (("libev: ev_periodic_start called with negative interval value", w->interval >= 0.));
            periodic_recalc (EV_A_ w);
        }
        else
            ev_at (w) = w->offset;
    
    
        ++periodiccnt;
        ev_start (EV_A_ (W)w, periodiccnt + HEAP0 - 1);
        array_needsize (ANHE, periodics, periodicmax, ev_active (w) + 1, EMPTY2);
        ANHE_w (periodics [ev_active (w)]) = (WT)w;
        ANHE_at_cache (periodics [ev_active (w)]);
        upheap (periodics, ev_active (w));
    }
    

            共有三种设置超时时间at的方法:

            a:如果reschedule_cb不为空,则忽略interval和offset,而使用reschedule_cb函数设置超时时间at,该函数以ev_rt_now为参数,设置下次超时事件触发的时间,每次重新设置at的时候(periodics_reschedule,periodics_reify),都会调用该函数。该函数的一个例子如下:

    static ev_tstamp my_rescheduler (ev_periodic *w, ev_tstamp now)
    {
        return now + 60.;
    }
    

            这就是将at设置为1分钟之后的时间点。

     

            b:reschedule_cb为空,interval>0,这种情况下,调用periodic_recalc设置at。该函数的作用就是将at置为下一个的offset + N*interval时间点,其中的offset一般处于[0, interval]范围内。比如置offset为0,interval为3600,意味着当系统时间是完整的1小时的时候,也就是系统时间可以被3600整除的时候,比如8:00,9:00等,就会触发超时事件。periodic_recalc的代码见下面。

     

            c:如果reschedule_cb为空,interval为0,则直接将at置为offset。这是一种绝对值,这种情况下,该监视器不会重复触发,触发一次之后就会停止监视器;而且该监视器也会无视时间调整,比如置at为20110101000000,则只要系统日历时间超过了改时间,就会触发超时事件。

     

            设置好at之后,就是将该监视器加入到堆periodics中,这与ev_timer的代码是一样的,不再赘述。

     

    3:periodic_recalc重新计算下一个触发时间点

    void periodic_recalc (struct ev_loop *loop, ev_periodic *w)
    {
        ev_tstamp interval = w->interval > MIN_INTERVAL ? w->interval : MIN_INTERVAL;
        ev_tstamp at = w->offset + interval * ev_floor ((ev_rt_now - w->offset) / interval);
    
        while (at <= ev_rt_now)
        {
            ev_tstamp nat = at + w->interval;
    
            if (expect_false (nat == at))
            {
                at = ev_rt_now;
                break;
            }
    
            at = nat;
        }
        ev_at (w) = at;
    }

             该函数的作用就是将at置为下一个的offset + N*interval时间点。ev_floor(x)返回小于x,且最接近x的整数。

             举个例子可能会容易明白该代码:interval为10分钟(600),offset为2分钟(120),表示将at置为下一个分钟数为2的时间点。

             假设当前为8:01:23,则最终会使得at为8:02:00。计算过程是 :interval * ev_floor ((ev_rt_now - w->offset) / interval)就表示7:50:00,然后再加上offset就是7:52:00,进入循环,最终调整得at=8:02:00。

             假设当前为8:03:56,则最终会使得at为8:12:00。计算过程是:interval * ev_floor ((ev_rt_now -w->offset) / interval)就表示8:00:00,然后再加上offset就是8:02:00,进入循环,最终调整得at=8:12:00。

     

             4:停止超时监视器ev_periodic_stop

    void ev_periodic_stop (struct ev_loop *loop, ev_periodic *w)
    {
        clear_pending (EV_A_ (W)w);
        if (expect_false (!ev_is_active (w)))
            return;
    
        int active = ev_active (w);
    
    
        --periodiccnt;
    
        if (expect_true (active < periodiccnt + HEAP0))
        {
            periodics [active] = periodics [periodiccnt + HEAP0];
            adjustheap (periodics, periodiccnt, active);
        }
        ev_stop (EV_A_ (W)w);
    }

             代码与ev_timer_stop几乎完全一致,不再赘述。

     

             5:重新调整超时时间periodics_reschedule

    static void periodics_reschedule (struct ev_loop *loop)
    {
        int i;
    
        for (i = HEAP0; i < periodiccnt + HEAP0; ++i)
        {
            ev_periodic *w = (ev_periodic *)ANHE_w (periodics [i]);
    
            if (w->reschedule_cb)
                ev_at (w) = w->reschedule_cb (w, ev_rt_now);
            else if (w->interval)
                periodic_recalc (EV_A_ w);
    
            ANHE_at_cache (periodics [i]);
        }
    
        reheap (periodics, periodiccnt);
    }

             在time_update中,如果发现日历时间被调整了,则会调用periodics_reschedule函数,调整ev_periodic的超时时间点at。调整的方法跟ev_periodic_start中的一样,要么使用reschedule_cb函数调整,要么就是调用periodic_recalc重新计算at。最后,将periodics堆中所有元素都调整完毕后,调用reheap使periodics恢复堆结构。

     

             6:将激活的超时事件排队periodics_reify

    void periodics_reify (struct ev_loop *loop)
    {
        while (periodiccnt && ANHE_at (periodics [HEAP0]) < ev_rt_now)
        {
            do{
                ev_periodic *w = (ev_periodic *)ANHE_w (periodics [HEAP0]);
    
                if (w->reschedule_cb)
                {
                    ev_at (w) = w->reschedule_cb (w, ev_rt_now);
                    assert (("libev: ev_periodic reschedule callback returned time in the past", ev_at (w) >= ev_rt_now));
    
                    ANHE_at_cache (periodics [HEAP0]);
                    downheap (periodics, periodiccnt, HEAP0);
                }
                else if (w->interval)
                {
                    periodic_recalc (EV_A_ w);
                    ANHE_at_cache (periodics [HEAP0]);
                    downheap (periodics, periodiccnt, HEAP0);
                }
                else
                    ev_periodic_stop (EV_A_ w); 
    
                feed_reverse (EV_A_ (W)w);
            }
            while (periodiccnt && ANHE_at (periodics [HEAP0]) < ev_rt_now);
    
            feed_reverse_done (EV_A_ EV_PERIODIC);
        }
    }

             主要流程跟timers_reify一样,只不过在重新计算下次触发时间点at的时候,计算方法跟ev_periodic_start中的一样。

     

    三:例子

    ev_periodic pw;
    
    void periodic_action(struct ev_loop *main_loop,ev_periodic *timer_w,int e)
    {
        time_t now;
        now = time(NULL);
        printf("cur time is %s
    ", ctime(&now));
    }
    
    static ev_tstamp my_rescheduler (ev_periodic *w, ev_tstamp now)
    {
        return now+120;
    }
    
    int main()
    {
        time_t now;
        now = time(NULL);
    
        struct ev_loop *main_loop = ev_default_loop(0);
    
        ev_periodic_init(&pw, periodic_action, 0, 0, my_rescheduler);	//1
        //ev_periodic_init(&pw, periodic_action, 120, 600, NULL);			//2
        //ev_periodic_init(&pw, periodic_action, now+20, 0, NULL);		//3
        ev_periodic_start(main_loop,&pw);
    
        printf("begin time time is %s
    ", ctime(&now));
    
        ev_run(main_loop,0);
        return;
    }
    

             采用第一种初始化方法:

    ev_periodic_init(&pw, periodic_action, 0, 0, my_rescheduler);

             结果是:

    begin time time is Thu Oct 29 21:33:05 2015
    
    cur time is Thu Oct 29 21:35:05 2015
    cur time is Thu Oct 29 21:37:05 2015
    cur time is Thu Oct 29 21:39:05 2015
    cur time is Thu Oct 29 21:41:05 2015
    ...
    

             采用第二种初始化方法:

    ev_periodic_init(&pw, periodic_action, 120, 600, NULL);

             结果是:

    begin time time is Thu Oct 29 21:38:29 2015
    
    cur time is Thu Oct 29 21:42:00 2015
    cur time is Thu Oct 29 21:52:00 2015
    cur time is Thu Oct 29 22:02:00 2015
    cur time is Thu Oct 29 22:12:00 2015
    cur time is Thu Oct 29 22:22:00 2015
    ...
    

             采用第三种初始化方法:

    ev_periodic_init(&pw, periodic_action, now+20, 0, NULL);

             结果是:

    begin time time is Thu Oct 29 21:39:03 2015
    
    cur time is Thu Oct 29 21:39:23 2015


    超时监视器流程图



  • 相关阅读:
    FineUI 单击菜单页面内容完全刷新,关闭Tab
    FineUI秘密花园(九) — 表单验证
    FineUI 选中多行获取行ID
    NPOI控制Excel格式
    ”那个人样子好怪。” “我也看到了,他好像一条狗。”
    投资条件
    setContentView+LayoutInflater=完美切换页面(两者一定要同时使用,setContentView提高切换页面速度必看)setContentView的秘密----续上
    设置PlaceHolder的颜色
    android 数据库
    行动,行动,再行动
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247100.html
Copyright © 2020-2023  润新知