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

一:数据结构

超时监视器ev_ periodic结构:

  1. typedef struct ev_periodic
  2. {
  3. int active;
  4. int pending;
  5. int priority;
  6. void *data;
  7. void (*cb)(struct ev_loop *loop, struct ev_periodic *w, int revents);
  8. ev_tstamp at;
  9.  
  10. ev_tstamp offset; /* rw */
  11. ev_tstamp interval; /* rw */
  12. ev_tstamp (*reschedule_cb)(struct ev_periodic *w, ev_tstamp now) EV_THROW; /* rw */
  13. } ev_periodic;

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

二:监视器函数

1:设置超时监视器

  1. #define ev_periodic_set(ev,ofs_,ival_,rcb_) do {\
  2. (ev)->offset = (ofs_); \
  3. (ev)->interval = (ival_); \
  4. (ev)->reschedule_cb = (rcb_); \
  5. }while (0)
  6.  
  7. #define ev_periodic_init(ev,cb,ofs,ival,rcb) do {\
  8. ev_init ((ev), (cb)); \
  9. ev_periodic_set ((ev),(ofs),(ival),(rcb)); \
  10. } while (0)

2:启动监视器ev_periodic_start

  1. void ev_periodic_start (struct ev_loop *loop, ev_periodic *w)
  2. {
  3. if (expect_false (ev_is_active (w)))
  4. return;
  5.  
  6. if (w->reschedule_cb)
  7. ev_at (w) = w->reschedule_cb (w, ev_rt_now);
  8. else if (w->interval)
  9. {
  10. assert (("libev: ev_periodic_start called with negative interval value", w->interval >= 0.));
  11. periodic_recalc (EV_A_ w);
  12. }
  13. else
  14. ev_at (w) = w->offset;
  15.  
  16. ++periodiccnt;
  17. ev_start (EV_A_ (W)w, periodiccnt + HEAP0 - 1);
  18. array_needsize (ANHE, periodics, periodicmax, ev_active (w) + 1, EMPTY2);
  19. ANHE_w (periodics [ev_active (w)]) = (WT)w;
  20. ANHE_at_cache (periodics [ev_active (w)]);
  21. upheap (periodics, ev_active (w));
  22. }

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

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

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

这就是将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重新计算下一个触发时间点

  1. void periodic_recalc (struct ev_loop *loop, ev_periodic *w)
  2. {
  3. ev_tstamp interval = w->interval > MIN_INTERVAL ? w->interval : MIN_INTERVAL;
  4. ev_tstamp at = w->offset + interval * ev_floor ((ev_rt_now - w->offset) / interval);
  5.  
  6. while (at <= ev_rt_now)
  7. {
  8. ev_tstamp nat = at + w->interval;
  9.  
  10. if (expect_false (nat == at))
  11. {
  12. at = ev_rt_now;
  13. break;
  14. }
  15.  
  16. at = nat;
  17. }
  18. ev_at (w) = at;
  19. }

该函数的作用就是将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

  1. void ev_periodic_stop (struct ev_loop *loop, ev_periodic *w)
  2. {
  3. clear_pending (EV_A_ (W)w);
  4. if (expect_false (!ev_is_active (w)))
  5. return;
  6.  
  7. int active = ev_active (w);
  8.  
  9. --periodiccnt;
  10.  
  11. if (expect_true (active < periodiccnt + HEAP0))
  12. {
  13. periodics [active] = periodics [periodiccnt + HEAP0];
  14. adjustheap (periodics, periodiccnt, active);
  15. }
  16. ev_stop (EV_A_ (W)w);
  17. }

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

5:重新调整超时时间periodics_reschedule

  1. static void periodics_reschedule (struct ev_loop *loop)
  2. {
  3. int i;
  4.  
  5. for (i = HEAP0; i < periodiccnt + HEAP0; ++i)
  6. {
  7. ev_periodic *w = (ev_periodic *)ANHE_w (periodics [i]);
  8.  
  9. if (w->reschedule_cb)
  10. ev_at (w) = w->reschedule_cb (w, ev_rt_now);
  11. else if (w->interval)
  12. periodic_recalc (EV_A_ w);
  13.  
  14. ANHE_at_cache (periodics [i]);
  15. }
  16.  
  17. reheap (periodics, periodiccnt);
  18. }

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

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

  1. void periodics_reify (struct ev_loop *loop)
  2. {
  3. while (periodiccnt && ANHE_at (periodics [HEAP0]) < ev_rt_now)
  4. {
  5. do{
  6. ev_periodic *w = (ev_periodic *)ANHE_w (periodics [HEAP0]);
  7.  
  8. if (w->reschedule_cb)
  9. {
  10. ev_at (w) = w->reschedule_cb (w, ev_rt_now);
  11. assert (("libev: ev_periodic reschedule callback returned time in the past", ev_at (w) >= ev_rt_now));
  12.  
  13. ANHE_at_cache (periodics [HEAP0]);
  14. downheap (periodics, periodiccnt, HEAP0);
  15. }
  16. else if (w->interval)
  17. {
  18. periodic_recalc (EV_A_ w);
  19. ANHE_at_cache (periodics [HEAP0]);
  20. downheap (periodics, periodiccnt, HEAP0);
  21. }
  22. else
  23. ev_periodic_stop (EV_A_ w);
  24.  
  25. feed_reverse (EV_A_ (W)w);
  26. }
  27. while (periodiccnt && ANHE_at (periodics [HEAP0]) < ev_rt_now);
  28.  
  29. feed_reverse_done (EV_A_ EV_PERIODIC);
  30. }
  31. }

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

三:例子

  1. ev_periodic pw;
  2.  
  3. void periodic_action(struct ev_loop *main_loop,ev_periodic *timer_w,int e)
  4. {
  5. time_t now;
  6. now = time(NULL);
  7. printf("cur time is %s\n", ctime(&now));
  8. }
  9.  
  10. static ev_tstamp my_rescheduler (ev_periodic *w, ev_tstamp now)
  11. {
  12. return now+120;
  13. }
  14.  
  15. int main()
  16. {
  17. time_t now;
  18. now = time(NULL);
  19.  
  20. struct ev_loop *main_loop = ev_default_loop(0);
  21.  
  22. ev_periodic_init(&pw, periodic_action, 0, 0, my_rescheduler); //1
  23. //ev_periodic_init(&pw, periodic_action, 120, 600, NULL); //2
  24. //ev_periodic_init(&pw, periodic_action, now+20, 0, NULL); //3
  25. ev_periodic_start(main_loop,&pw);
  26.  
  27. printf("begin time time is %s\n", ctime(&now));
  28.  
  29. ev_run(main_loop,0);
  30. return;
  31. }

采用第一种初始化方法:

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

结果是:

  1. begin time time is Thu Oct 29 21:33:05 2015
  2.  
  3. cur time is Thu Oct 29 21:35:05 2015
  4. cur time is Thu Oct 29 21:37:05 2015
  5. cur time is Thu Oct 29 21:39:05 2015
  6. cur time is Thu Oct 29 21:41:05 2015
  7. ...

采用第二种初始化方法:

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

结果是:

  1. begin time time is Thu Oct 29 21:38:29 2015
  2.  
  3. cur time is Thu Oct 29 21:42:00 2015
  4. cur time is Thu Oct 29 21:52:00 2015
  5. cur time is Thu Oct 29 22:02:00 2015
  6. cur time is Thu Oct 29 22:12:00 2015
  7. cur time is Thu Oct 29 22:22:00 2015
  8. ...

采用第三种初始化方法:

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

结果是:

  1. begin time time is Thu Oct 29 21:39:03 2015
  2.  
  3. cur time is Thu Oct 29 21:39:23 2015



超时监视器流程图





Libev源码分析05:Libev中的绝对时间定时器的更多相关文章

  1. [转]Libev源码分析 -- 整体设计

    Libev源码分析 -- 整体设计 libev是Marc Lehmann用C写的高性能事件循环库.通过libev,可以灵活地把各种事件组织管理起来,如:时钟.io.信号等.libev在业界内也是广受好 ...

  2. NIO 源码分析(05) Channel 源码分析

    目录 一.Channel 类图 二.begin 和 close 是什么 2.1 AbstractInterruptibleChannel 中的 begin 和 close 2.2 Selector 中 ...

  3. angular源码分析:angular中脏活累活的承担者之$interpolate

    一.首先抛出两个问题 问题一:在angular中我们绑定数据最基本的方式是用两个大括号将$scope的变量包裹起来,那么如果想将大括号换成其他什么符号,比如换成[{与}],可不可以呢,如果可以在哪里配 ...

  4. angular源码分析:angular中入境检察官$sce

    一.ng-bing-html指令问题 需求:我需要将一个变量$scope.x = '<a href="http://www.cnblogs.com/web2-developer/&qu ...

  5. angular源码分析:angular中各种常用函数,比较省代码的各种小技巧

    angular的工具函数 在angular的API文档中,在最前面就是讲的就是angular的工具函数,下面列出来 angular.bind //用户将函数和对象绑定在一起,返回一个新的函数 angu ...

  6. angular源码分析:angular中的依赖注入式如何实现的

    一.准备 angular的源码一份,我这里使用的是v1.4.7.源码的获取,请参考我另一篇博文:angular源码分析:angular源代码的获取与编译环境安装 二.什么是依赖注入 据我所知,依赖注入 ...

  7. Libev源码分析09:select突破处理描述符个数的限制

    众所周知,Linux下的多路复用函数select采用描述符集表示处理的描述符.描述符集的大小就是它所能处理的最大描述符限制.通常情况下该值为1024,等同于每个进程所能打开的描述符个数. 增大描述符集 ...

  8. angular源码分析:angular中$rootscope的实现——scope的一生

    在angular中,$scope是一个关键的服务,可以被注入到controller中,注入其他服务却只能是$rootscope.scope是一个概念,是一个类,而$rootscope和被注入到cont ...

  9. angular源码分析:angular中jqLite的实现——你可以丢掉jQuery了

    一.从function JQLite(element)函数开始. function JQLite(element) { if (element instanceof JQLite) { //情况1 r ...

随机推荐

  1. 读书笔记--Struts 2 in Action 目录

    1.Struts 2:现代Web框架 1.1 web应用程序:快速学习 21.1.1 构建web应用程序 21.1.2 基础技术简介 31.1.3 深入研究 61.2 web应用程序框架 71.2.1 ...

  2. Linux学习(一):软链接和硬链接

    今天起,决定开始自学Linux命令及Shell脚本,并用Linux学习(命令行,Shell及其他知识点)这一系列记录下自己的心路历程,内容不分先后,只记录自己觉得有必要的,简单的就不记了! 第一个知识 ...

  3. idea中查看方法的调用链

    Eclipse的"Call Hierarchy"可以查看一个Java方法或类成员变量的调用树(caller和callee两个方向),非常方便.  在IDEA中类似功能被划分到了三个 ...

  4. PAI-STUDIO通过Tensorflow处理MaxCompute表数据

    PAI-STUDIO在支持OSS数据源的基础上,增加了对MaxCompute表的数据支持.用户可以直接使用PAI-STUDIO的Tensorflow组件读写MaxCompute数据,本教程将提供完整数 ...

  5. 那些年,我们见过的Java服务端乱象

    导读 查尔斯·狄更斯在<双城记>中写道:“这是一个最好的时代,也是一个最坏的时代.”移动互联网的快速发展,出现了许多新机遇,很多创业者伺机而动:随着行业竞争加剧,互联网红利逐渐消失,很多创 ...

  6. LUGOU P3907 圈的异或

    传送门 解题思路 其实就是找出所有的环判断,因为数据范围很小直接暴力做,注意要判断自环. 代码 #include<iostream> #include<cstdio> #inc ...

  7. Codeforces Beta Round #77 (Div. 2 Only) A. Football【字符串/判断是否存在连续7个0或7个1】

    A. Football time limit per test 2 seconds memory limit per test 256 megabytes input standard input o ...

  8. 前端框架中 “类mixin” 模式的思考

    "类 mixin" 指的是 Vue 中的 mixin,Regular 中的 implement 使用 Mixin 的目的 首先我们需要知道为什么会有 mixin 的存在? 为了扩展 ...

  9. Codeforces 404B

    毫无疑问这题不是难题,但是这种题目最让人纠结 打心里对这种题目就比较害怕,果然,各种WE 这里贴上代码,用Python写的,比较偷懒: def cur_pos(a, d): if 0 <= d ...

  10. date、cal和clear命令

    一.date命令 date命令的功能:date命令是显示或设置系统时间与日期. 很多shell脚本里面需要打印不同格式的时间或日期,以及要根据时间和日期执行操作.延时通常用于脚本执行过程中提供一段等待 ...