Libevent时间管理

来源:互联网 发布:卡尔曼滤波跟踪算法 编辑:程序博客网 时间:2024/06/07 07:04

转载自: http://blog.csdn.net/luotuo44/article/details/38661787




基本时间操作函数:

        Libevent采用的时间类型是struct  timeval,这个类型在很多平台都提供了。此外,Libevent还提供了一系列的时间操作函数。比如两个struct timeval相加、相减、比较大小。有些平台直接提供了一些时间操作函数,但有些则没有,那么Libevent就自己实现。这些宏如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #ifdef _EVENT_HAVE_TIMERADD  
  2. #define evutil_timeradd(tvp, uvp, vvp) timeradd((tvp), (uvp), (vvp))  
  3. #define evutil_timersub(tvp, uvp, vvp) timersub((tvp), (uvp), (vvp))  
  4. #else  
  5. #define evutil_timeradd(tvp, uvp, vvp)                  \  
  6.     do {                                \  
  7.         (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec;     \  
  8.         (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec;       \  
  9.         if ((vvp)->tv_usec >= 1000000) {          \  
  10.             (vvp)->tv_sec++;             \  
  11.             (vvp)->tv_usec -= 1000000;           \  
  12.         }                           \  
  13.     } while (0)  
  14. #define evutil_timersub(tvp, uvp, vvp)                  \  
  15.     do {                                \  
  16.         (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec;     \  
  17.         (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec;  \  
  18.         if ((vvp)->tv_usec < 0) {             \  
  19.             (vvp)->tv_sec--;             \  
  20.             (vvp)->tv_usec += 1000000;           \  
  21.         }                           \  
  22.     } while (0)  
  23. #endif   
  24.   
  25. #ifdef _EVENT_HAVE_TIMERCLEAR  
  26. #define evutil_timerclear(tvp) timerclear(tvp)  
  27. #else  
  28. #define evutil_timerclear(tvp)  (tvp)->tv_sec = (tvp)->tv_usec = 0  
  29. #endif  
  30.   
  31.   
  32. #define evutil_timercmp(tvp, uvp, cmp)                  \  
  33.     (((tvp)->tv_sec == (uvp)->tv_sec) ?               \  
  34.      ((tvp)->tv_usec cmp (uvp)->tv_usec) :                \  
  35.      ((tvp)->tv_sec cmp (uvp)->tv_sec))  
  36.   
  37. #ifdef _EVENT_HAVE_TIMERISSET  
  38. #define evutil_timerisset(tvp) timerisset(tvp)  
  39. #else  
  40. #define evutil_timerisset(tvp)  ((tvp)->tv_sec || (tvp)->tv_usec)  
  41. #endif  
        代码中的那些条件宏,是在配置Libevent的时候检查所在的系统环境而定义的。具体的内容,可以参考《event-config.h指明所在系统的环境》一文。

        Libevent的时间一般是用在超时event的。对于超时event,用户只需给出一个超时时间,比如多少秒,而不是一个绝对时间。但在Libevent内部,要将这个时间转换成绝对时间。所以在Libevent内部会经常获取系统时间(绝对时间),然后进行一些处理,比如,转换、比较。


cache时间:

        Libevent封装了一个evutil_gettimeofday函数用来获取系统时间,该函数在POSIX的系统是直接调用gettimeofday函数,在Windows系统是通过_ftime函数。虽然gettimeofday的耗时成本不大,不过Libevent还是使用了一个cache保存时间,使得更加高效。在event_base结构体有一个struct timeval类型的cache变量 tv_cache。处理超时event的两个函数event_add_internal和event_base_loop内部都是调用gettime函数获取时间的。gettime函数如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //event.c文件  
  2. static int  
  3. gettime(struct event_base *base, struct timeval *tp)  
  4. {  
  5.     if (base->tv_cache.tv_sec) { //cache可用  
  6.         *tp = base->tv_cache;  
  7.         return (0);  
  8.     }  
  9.   
  10.     …//没有cache的时候就使用其他方式获取时间  
  11. }  

        从上面代码可以看到,Libevent优先使用cache时间。tv_bache变量处理作为cache外,还有另外一个作用,下面会讲到。

        cache的时间也是通过调用系统的提供的时间函数得到的。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //event.c文件  
  2. static inline void  
  3. update_time_cache(struct event_base *base)  
  4. {  
  5.     base->tv_cache.tv_sec = 0;  
  6.     if (!(base->flags & EVENT_BASE_FLAG_NO_CACHE_TIME))  
  7.         gettime(base, &base->tv_cache);  
  8. }  

        tv_cache是通过调用gettime来获取时间。由于tv_cache.tv_sec已经赋值为0,所以它将使用系统提供的时间函数得到时间。代码也展示了,如果event_base的配置中定义了EVENT_BASE_FLAG_NO_CACHE_TIME宏,将不能使用cache时间。关于这个宏的设置可以参考《配置event_base》一文。



处理用户手动修改系统时间:

        如果用户能老老实实,或许代码就不需要写得很复杂。由于用户的不老实,所以有时候要考虑很多很特殊的情况。在Libevent的时间管理这方面也是如此。

        Libevent在实际使用时还有一个坑爹的现象,那就是,用户手动把时钟(wall time)往回调了。比如说现在是上午9点,但用户却把OS的系统时间调成了上午7点。这是很坑爹的。对于超时event和event_add的第二个参数,都是一个时间长度。但在内部Libevent要把这个时间转换成绝对时间。

         如果用户手动修改了OS的系统时间。那么Libevent把超时时间长度转换成绝对时间将是弄巧成拙。拿上面的时间例子。如果用户设置的超时为1分钟。那么到了9:01就会超时。如果用户把系统时间调成了7点,那么要过2个小时01分才能发生超时。这就和用户原先的设置差得很远了。

        读者可能会说,这个责任应该是由用户负。呵呵,但Libevent提供的函数接口是一个时间长度,既然是时间长度,那么无论用户怎么改变OS的系统时间,这个时间长度都是相对于event_add ()被调用的那一刻算起,这是不会变的。如果Libevent做不到这一点,这说明是Libevent没有遵循接口要求。

 

        为此,Libevent提出了一些解决方案。

使用monotonic时间:

        问题的由来是因为用户能修改系统时间,所以最简单的解决方案就是能获取到一个用户不能修改的时间,然后以之为绝对时间。因为event_add提供给用户的接口使用的是一个时间长度,所以无论是使用哪个绝对时间都是无所谓的。

        基于这一点,Libevent找到了monotonic时间,从字面来看monotonic翻译成单调。我们高中学过的单调函数英文名就是monotonic function。monotonic时间就像单调递增函数那样,只增不减的,没有人能手动修改之。

        monotonic时间是boot启动后到现在的时间。用户是不能修改这个时间。如果Libevent所在的系统支持monotonic时间的话,那么Libevent就会选用这个monotonic时间为绝对时间。

 

        首先,Libevent检查所在的系统是否支持monotonic时间。在event_base_new_with_config函数中会调用detect_monotonic函数检测。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //event.c文件  
  2. static void  
  3. detect_monotonic(void)  
  4. {  
  5. #if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)  
  6.     struct timespec ts;  
  7.     static int use_monotonic_initialized = 0;  
  8.   
  9.     if (use_monotonic_initialized)  
  10.         return;  
  11.   
  12.     if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)  
  13.         use_monotonic = 1; //系统支持monotonic时间  
  14.     use_monotonic_initialized = 1;  
  15. #endif  
  16. }  

        从上面代码可以看到,如果Libevent所在的系统支持monotonic时间,就将全局变量use_monotonic赋值1,作为标志。

 

        如果Libevent所在的系统支持monotonic时间,那么Libevent将使用monotonic时间,也就是说Libevent用于获取系统时间的函数gettime将由monotonic提供时间。
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //event.c文件  
  2. static int  
  3. gettime(struct event_base *base, struct timeval *tp)  
  4. {  
  5.     EVENT_BASE_ASSERT_LOCKED(base);  
  6.   
  7.     if (base->tv_cache.tv_sec) {  
  8.         *tp = base->tv_cache;  
  9.         return (0);  
  10.     }  
  11.   
  12. #if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)  
  13.     if (use_monotonic) {  
  14.         struct timespec ts;  
  15.   
  16.         if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)  
  17.             return (-1);  
  18.   
  19.         tp->tv_sec = ts.tv_sec;  
  20.         tp->tv_usec = ts.tv_nsec / 1000;  
  21.           
  22.         //额外的功能  
  23.         if (base->last_updated_clock_diff + CLOCK_SYNC_INTERVAL  
  24.             < ts.tv_sec) {  
  25.             struct timeval tv;  
  26.             evutil_gettimeofday(&tv,NULL);  
  27.             //tv_clock_diff记录两种时间的时间差  
  28.             evutil_timersub(&tv, tp, &base->tv_clock_diff);  
  29.             base->last_updated_clock_diff = ts.tv_sec;  
  30.         }  
  31.   
  32.         return (0);  
  33.     }  
  34. #endif  
  35.   
  36.     //如果所在的系统不支持monotonic时间,那么只能使用evutil_gettimeofday了  
  37.     return (evutil_gettimeofday(tp, NULL));  
  38. }  

        上面的代码虽然首先是使用cache时间,但实际上event_base结构体的cache时间也是通过调用gettime函数而得到的。上面代码也可以看到:如果所在的系统没有提供monotonic时间,那么就只能使用evutil_gettimeofday这个函数提供的系统时间了。

 

        从上面的分析可知,如果Libevent所在的系统支持monotonic时间,那么根本就不用考虑用户手动修改系统时间这坑爹的事情。但如果所在的系统没有支持monotonic时间,那么Libevent就只能使用evutil_gettimeofday获取一个用户能修改的时间。


尽可能精确记录时间差:

        现在来看一下Libevent在这种情况下在怎么解决这个坑爹得的问题。

         Libevent给出的方案是,尽可能精确地计算 用户往回调了多长时间。如果知道了用户往回调了多长时间,那么将小根堆中的全部event的时间都往回调一样的时间即可。Libevent调用timeout_correct函数处理这个问题。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //event.c文件  
  2. static void  
  3. timeout_correct(struct event_base *base, struct timeval *tv)  
  4. {  
  5.     /* Caller must hold th_base_lock. */  
  6.     struct event **pev;  
  7.     unsigned int size;  
  8.     struct timeval off;  
  9.     int i;  
  10.   
  11.     //如果系统支持monotonic时间,那么就不需要校准时间了  
  12.     if (use_monotonic)  
  13.         return;  
  14.   
  15.     //获取现在的系统时间  
  16.     gettime(base, tv);  
  17.   
  18.   
  19.     //tv的时间更大,说明用户没有往回调系统时间。那么不需要处理  
  20.     if (evutil_timercmp(tv, &base->event_tv, >=)) {  
  21.         base->event_tv = *tv;  
  22.         return;  
  23.     }  
  24.   
  25.     evutil_timersub(&base->event_tv, tv, &off);//off差值,即用户调小了多少  
  26.   
  27.     pev = base->timeheap.p;  
  28.     size = base->timeheap.n;  
  29.     //用户已经修改了OS的系统时间。现在需要对小根堆的所有event  
  30.     //都修改时间。使得之适应新的系统时间  
  31.     for (; size-- > 0; ++pev) {  
  32.         struct timeval *ev_tv = &(**pev).ev_timeout;  
  33.         //前面已经用off保存了,用户调小了多少。现在只需  
  34.         //将小根堆的所有event的超时时间(绝对时间)都减去这个off即可  
  35.         evutil_timersub(ev_tv, &off, ev_tv);  
  36.     }  
  37.   
  38.     //保存现在的系统时间。以防用户再次修改系统时间  
  39.     base->event_tv = *tv;  
  40. }  

        Libevent用event_base的成员变量event_tv保存用户修改系统时间前的系统时间。如果刚保存完,用户就修改系统时间,这样就能精确地计算出用户往回调了多长时间。但毕竟Libevent是用户态的库,不能做到用户修改系统时间前的一刻保存系统时间。

        于是Libevent采用多采点的方式,即时不时就保存一次系统时间。所以在event_base_loop函数中的while循环体里面会有gettime(base, &base->event_tv);这是为了能多采点。但这个while循环里面还会执行多路IO复用函数和处理被激活event的回调函数(这个回调函数执行多久也是个未知数)。这两个函数的执行需要的时间可能会比较长,如果用户刚才是在执行完这两个函数之后修改系统时间,那么event_tv保存的时间就不怎么精确了。这也是没有办法的啊!!唉!!

        下面贴出event_base_loop函数

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //event.c文件  
  2. int  
  3. event_base_loop(struct event_base *base, int flags)  
  4. {  
  5.     const struct eventop *evsel = base->evsel;  
  6.     struct timeval tv;  
  7.     struct timeval *tv_p;  
  8.     int res, done, retval = 0;  
  9.   
  10.     //要使用cache时间,得在配置event_base时,没有加入EVENT_BASE_FLAG_NO_CACHE_TIME选项  
  11.     clear_time_cache(base);  
  12.   
  13.   
  14.     while (!done) {  
  15.         timeout_correct(base, &tv);  
  16.   
  17.         tv_p = &tv;  
  18.         if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {  
  19.             //参考http://blog.csdn.net/luotuo44/article/details/38637671  
  20.             timeout_next(base, &tv_p); //获取dispatch的最大等待时间  
  21.         } else {  
  22.             evutil_timerclear(&tv);  
  23.         }  
  24.   
  25.         //保存系统时间。如果有cache,将保存cache时间。  
  26.         gettime(base, &base->event_tv);  
  27.   
  28.         //之所以要在进入dispatch之前清零,是因为进入  
  29.         //dispatch后,可能会等待一段时间。cache就没有意义了。  
  30.         //如果第二个线程此时想add一个event到这个event_base里面,在  
  31.         //event_add_internal函数中会调用gettime。如果cache不清零,  
  32.         //那么将会取这个cache时间。这将取一个不准确的时间。  
  33.         clear_time_cache(base);  
  34.   
  35.         //多路IO复用函数  
  36.         res = evsel->dispatch(base, tv_p);  
  37.   
  38.         //将系统时间赋值到cache中  
  39.         update_time_cache(base);  
  40.   
  41.         //处理超时事件。参考http://blog.csdn.net/luotuo44/article/details/38637671  
  42.         timeout_process(base);  
  43.   
  44.         if (N_ACTIVE_CALLBACKS(base)) {  
  45.             int n = event_process_active(base);//处理激活event  
  46.         }   
  47.     }  
  48.   
  49.     return (retval);  
  50. }  

        可以看到,在dispatch和event_process_active之间有一个update_time_cache。而前面的gettime(base,&base->event_tv);实际上取的就是cache的时间。所以,如果该Libevent支持cache的话,会精确那么一些。一般来说,用户为event设置的回调函数,不应该执行太久的时间。这也是tv_cache时间的另外一个作用。

 

出现的bug:

        由于Libevent的解决方法并不是很精确,所以还是会有一些bug。下面给出一个bug。如果用户是在调用event_new函数之后,event_add之前对系统时间进行修改,那么无论用户设置的event超时有多长,都会马上触发超时。下面给出实际的例子。这个例子要运行在不支持monotonic时间的系统,我是在Windows运行的。
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <event2/event.h>  
  2. #include<stdio.h>  
  3.   
  4.   
  5. void timeout_cb(int fd, short event, void *arg)  
  6. {  
  7.     printf("in the timeout_cb\n");  
  8. }  
  9.   
  10.   
  11. int main()  
  12. {  
  13.     struct event_base *base = event_base_new();  
  14.   
  15.     struct event *ev = event_new(base, -1, EV_TIMEOUT, timeout_cb, NULL);  
  16.   
  17.     int ch;  
  18. //暂停,让用户有时间修改系统时间。可以将系统时间往前1个小时  
  19.     scanf("%c", &ch);   
  20.   
  21.     struct timeval tv = {100, 0};//这个超时时长要比较长。这里取100秒  
  22.     //第二个参数不能为NULL.不然也是不能触发超时的。毕竟没有时间  
  23.     event_add(ev, &tv);  
  24.   
  25.     event_base_dispatch(base);  
  26.   
  27.     return 0;  
  28. }  

        这个bug的出现是因为,在event_base_new_with_config函数中有gettime(base,&base->event_tv),所以event_tv记录了修改前的时间。而event_add是在修改系统时间后才调用的。所以event结构体的ev_timeout变量使用的是修改系统时间后的超时时间,这是正确的时间。在执行timeout_correct函数时,Libevent发现用户修改了系统时间,所以就将本来正确的ev_timeout减去了off。所以ev_timeout就变得比较修改后的系统时间小了。在后面检查超时时,就会发现该event已经超时了(实际是没有超时),就把它触发。

        如果该event有EV_PERSIST属性,那么之后的超时则会是正确的。这个留给读者去分析吧。

 

        另外,Libevent并没有考虑把时钟往后调,比如现在是9点,用户把系统时间调成10点。上面的代码如果用户是在event_add之后修改系统时间,就能发现这个bug。

基本时间操作函数:

        Libevent采用的时间类型是struct  timeval,这个类型在很多平台都提供了。此外,Libevent还提供了一系列的时间操作函数。比如两个struct timeval相加、相减、比较大小。有些平台直接提供了一些时间操作函数,但有些则没有,那么Libevent就自己实现。这些宏如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #ifdef _EVENT_HAVE_TIMERADD  
  2. #define evutil_timeradd(tvp, uvp, vvp) timeradd((tvp), (uvp), (vvp))  
  3. #define evutil_timersub(tvp, uvp, vvp) timersub((tvp), (uvp), (vvp))  
  4. #else  
  5. #define evutil_timeradd(tvp, uvp, vvp)                  \  
  6.     do {                                \  
  7.         (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec;     \  
  8.         (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec;       \  
  9.         if ((vvp)->tv_usec >= 1000000) {          \  
  10.             (vvp)->tv_sec++;             \  
  11.             (vvp)->tv_usec -= 1000000;           \  
  12.         }                           \  
  13.     } while (0)  
  14. #define evutil_timersub(tvp, uvp, vvp)                  \  
  15.     do {                                \  
  16.         (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec;     \  
  17.         (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec;  \  
  18.         if ((vvp)->tv_usec < 0) {             \  
  19.             (vvp)->tv_sec--;             \  
  20.             (vvp)->tv_usec += 1000000;           \  
  21.         }                           \  
  22.     } while (0)  
  23. #endif   
  24.   
  25. #ifdef _EVENT_HAVE_TIMERCLEAR  
  26. #define evutil_timerclear(tvp) timerclear(tvp)  
  27. #else  
  28. #define evutil_timerclear(tvp)  (tvp)->tv_sec = (tvp)->tv_usec = 0  
  29. #endif  
  30.   
  31.   
  32. #define evutil_timercmp(tvp, uvp, cmp)                  \  
  33.     (((tvp)->tv_sec == (uvp)->tv_sec) ?               \  
  34.      ((tvp)->tv_usec cmp (uvp)->tv_usec) :                \  
  35.      ((tvp)->tv_sec cmp (uvp)->tv_sec))  
  36.   
  37. #ifdef _EVENT_HAVE_TIMERISSET  
  38. #define evutil_timerisset(tvp) timerisset(tvp)  
  39. #else  
  40. #define evutil_timerisset(tvp)  ((tvp)->tv_sec || (tvp)->tv_usec)  
  41. #endif  
        代码中的那些条件宏,是在配置Libevent的时候检查所在的系统环境而定义的。具体的内容,可以参考《event-config.h指明所在系统的环境》一文。

        Libevent的时间一般是用在超时event的。对于超时event,用户只需给出一个超时时间,比如多少秒,而不是一个绝对时间。但在Libevent内部,要将这个时间转换成绝对时间。所以在Libevent内部会经常获取系统时间(绝对时间),然后进行一些处理,比如,转换、比较。


cache时间:

        Libevent封装了一个evutil_gettimeofday函数用来获取系统时间,该函数在POSIX的系统是直接调用gettimeofday函数,在Windows系统是通过_ftime函数。虽然gettimeofday的耗时成本不大,不过Libevent还是使用了一个cache保存时间,使得更加高效。在event_base结构体有一个struct timeval类型的cache变量 tv_cache。处理超时event的两个函数event_add_internal和event_base_loop内部都是调用gettime函数获取时间的。gettime函数如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //event.c文件  
  2. static int  
  3. gettime(struct event_base *base, struct timeval *tp)  
  4. {  
  5.     if (base->tv_cache.tv_sec) { //cache可用  
  6.         *tp = base->tv_cache;  
  7.         return (0);  
  8.     }  
  9.   
  10.     …//没有cache的时候就使用其他方式获取时间  
  11. }  

        从上面代码可以看到,Libevent优先使用cache时间。tv_bache变量处理作为cache外,还有另外一个作用,下面会讲到。

        cache的时间也是通过调用系统的提供的时间函数得到的。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //event.c文件  
  2. static inline void  
  3. update_time_cache(struct event_base *base)  
  4. {  
  5.     base->tv_cache.tv_sec = 0;  
  6.     if (!(base->flags & EVENT_BASE_FLAG_NO_CACHE_TIME))  
  7.         gettime(base, &base->tv_cache);  
  8. }  

        tv_cache是通过调用gettime来获取时间。由于tv_cache.tv_sec已经赋值为0,所以它将使用系统提供的时间函数得到时间。代码也展示了,如果event_base的配置中定义了EVENT_BASE_FLAG_NO_CACHE_TIME宏,将不能使用cache时间。关于这个宏的设置可以参考《配置event_base》一文。



处理用户手动修改系统时间:

        如果用户能老老实实,或许代码就不需要写得很复杂。由于用户的不老实,所以有时候要考虑很多很特殊的情况。在Libevent的时间管理这方面也是如此。

        Libevent在实际使用时还有一个坑爹的现象,那就是,用户手动把时钟(wall time)往回调了。比如说现在是上午9点,但用户却把OS的系统时间调成了上午7点。这是很坑爹的。对于超时event和event_add的第二个参数,都是一个时间长度。但在内部Libevent要把这个时间转换成绝对时间。

         如果用户手动修改了OS的系统时间。那么Libevent把超时时间长度转换成绝对时间将是弄巧成拙。拿上面的时间例子。如果用户设置的超时为1分钟。那么到了9:01就会超时。如果用户把系统时间调成了7点,那么要过2个小时01分才能发生超时。这就和用户原先的设置差得很远了。

        读者可能会说,这个责任应该是由用户负。呵呵,但Libevent提供的函数接口是一个时间长度,既然是时间长度,那么无论用户怎么改变OS的系统时间,这个时间长度都是相对于event_add ()被调用的那一刻算起,这是不会变的。如果Libevent做不到这一点,这说明是Libevent没有遵循接口要求。

 

        为此,Libevent提出了一些解决方案。

使用monotonic时间:

        问题的由来是因为用户能修改系统时间,所以最简单的解决方案就是能获取到一个用户不能修改的时间,然后以之为绝对时间。因为event_add提供给用户的接口使用的是一个时间长度,所以无论是使用哪个绝对时间都是无所谓的。

        基于这一点,Libevent找到了monotonic时间,从字面来看monotonic翻译成单调。我们高中学过的单调函数英文名就是monotonic function。monotonic时间就像单调递增函数那样,只增不减的,没有人能手动修改之。

        monotonic时间是boot启动后到现在的时间。用户是不能修改这个时间。如果Libevent所在的系统支持monotonic时间的话,那么Libevent就会选用这个monotonic时间为绝对时间。

 

        首先,Libevent检查所在的系统是否支持monotonic时间。在event_base_new_with_config函数中会调用detect_monotonic函数检测。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //event.c文件  
  2. static void  
  3. detect_monotonic(void)  
  4. {  
  5. #if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)  
  6.     struct timespec ts;  
  7.     static int use_monotonic_initialized = 0;  
  8.   
  9.     if (use_monotonic_initialized)  
  10.         return;  
  11.   
  12.     if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)  
  13.         use_monotonic = 1; //系统支持monotonic时间  
  14.     use_monotonic_initialized = 1;  
  15. #endif  
  16. }  

        从上面代码可以看到,如果Libevent所在的系统支持monotonic时间,就将全局变量use_monotonic赋值1,作为标志。

 

        如果Libevent所在的系统支持monotonic时间,那么Libevent将使用monotonic时间,也就是说Libevent用于获取系统时间的函数gettime将由monotonic提供时间。
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //event.c文件  
  2. static int  
  3. gettime(struct event_base *base, struct timeval *tp)  
  4. {  
  5.     EVENT_BASE_ASSERT_LOCKED(base);  
  6.   
  7.     if (base->tv_cache.tv_sec) {  
  8.         *tp = base->tv_cache;  
  9.         return (0);  
  10.     }  
  11.   
  12. #if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)  
  13.     if (use_monotonic) {  
  14.         struct timespec ts;  
  15.   
  16.         if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)  
  17.             return (-1);  
  18.   
  19.         tp->tv_sec = ts.tv_sec;  
  20.         tp->tv_usec = ts.tv_nsec / 1000;  
  21.           
  22.         //额外的功能  
  23.         if (base->last_updated_clock_diff + CLOCK_SYNC_INTERVAL  
  24.             < ts.tv_sec) {  
  25.             struct timeval tv;  
  26.             evutil_gettimeofday(&tv,NULL);  
  27.             //tv_clock_diff记录两种时间的时间差  
  28.             evutil_timersub(&tv, tp, &base->tv_clock_diff);  
  29.             base->last_updated_clock_diff = ts.tv_sec;  
  30.         }  
  31.   
  32.         return (0);  
  33.     }  
  34. #endif  
  35.   
  36.     //如果所在的系统不支持monotonic时间,那么只能使用evutil_gettimeofday了  
  37.     return (evutil_gettimeofday(tp, NULL));  
  38. }  

        上面的代码虽然首先是使用cache时间,但实际上event_base结构体的cache时间也是通过调用gettime函数而得到的。上面代码也可以看到:如果所在的系统没有提供monotonic时间,那么就只能使用evutil_gettimeofday这个函数提供的系统时间了。

 

        从上面的分析可知,如果Libevent所在的系统支持monotonic时间,那么根本就不用考虑用户手动修改系统时间这坑爹的事情。但如果所在的系统没有支持monotonic时间,那么Libevent就只能使用evutil_gettimeofday获取一个用户能修改的时间。


尽可能精确记录时间差:

        现在来看一下Libevent在这种情况下在怎么解决这个坑爹得的问题。

         Libevent给出的方案是,尽可能精确地计算 用户往回调了多长时间。如果知道了用户往回调了多长时间,那么将小根堆中的全部event的时间都往回调一样的时间即可。Libevent调用timeout_correct函数处理这个问题。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //event.c文件  
  2. static void  
  3. timeout_correct(struct event_base *base, struct timeval *tv)  
  4. {  
  5.     /* Caller must hold th_base_lock. */  
  6.     struct event **pev;  
  7.     unsigned int size;  
  8.     struct timeval off;  
  9.     int i;  
  10.   
  11.     //如果系统支持monotonic时间,那么就不需要校准时间了  
  12.     if (use_monotonic)  
  13.         return;  
  14.   
  15.     //获取现在的系统时间  
  16.     gettime(base, tv);  
  17.   
  18.   
  19.     //tv的时间更大,说明用户没有往回调系统时间。那么不需要处理  
  20.     if (evutil_timercmp(tv, &base->event_tv, >=)) {  
  21.         base->event_tv = *tv;  
  22.         return;  
  23.     }  
  24.   
  25.     evutil_timersub(&base->event_tv, tv, &off);//off差值,即用户调小了多少  
  26.   
  27.     pev = base->timeheap.p;  
  28.     size = base->timeheap.n;  
  29.     //用户已经修改了OS的系统时间。现在需要对小根堆的所有event  
  30.     //都修改时间。使得之适应新的系统时间  
  31.     for (; size-- > 0; ++pev) {  
  32.         struct timeval *ev_tv = &(**pev).ev_timeout;  
  33.         //前面已经用off保存了,用户调小了多少。现在只需  
  34.         //将小根堆的所有event的超时时间(绝对时间)都减去这个off即可  
  35.         evutil_timersub(ev_tv, &off, ev_tv);  
  36.     }  
  37.   
  38.     //保存现在的系统时间。以防用户再次修改系统时间  
  39.     base->event_tv = *tv;  
  40. }  

        Libevent用event_base的成员变量event_tv保存用户修改系统时间前的系统时间。如果刚保存完,用户就修改系统时间,这样就能精确地计算出用户往回调了多长时间。但毕竟Libevent是用户态的库,不能做到用户修改系统时间前的一刻保存系统时间。

        于是Libevent采用多采点的方式,即时不时就保存一次系统时间。所以在event_base_loop函数中的while循环体里面会有gettime(base, &base->event_tv);这是为了能多采点。但这个while循环里面还会执行多路IO复用函数和处理被激活event的回调函数(这个回调函数执行多久也是个未知数)。这两个函数的执行需要的时间可能会比较长,如果用户刚才是在执行完这两个函数之后修改系统时间,那么event_tv保存的时间就不怎么精确了。这也是没有办法的啊!!唉!!

        下面贴出event_base_loop函数

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //event.c文件  
  2. int  
  3. event_base_loop(struct event_base *base, int flags)  
  4. {  
  5.     const struct eventop *evsel = base->evsel;  
  6.     struct timeval tv;  
  7.     struct timeval *tv_p;  
  8.     int res, done, retval = 0;  
  9.   
  10.     //要使用cache时间,得在配置event_base时,没有加入EVENT_BASE_FLAG_NO_CACHE_TIME选项  
  11.     clear_time_cache(base);  
  12.   
  13.   
  14.     while (!done) {  
  15.         timeout_correct(base, &tv);  
  16.   
  17.         tv_p = &tv;  
  18.         if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {  
  19.             //参考http://blog.csdn.net/luotuo44/article/details/38637671  
  20.             timeout_next(base, &tv_p); //获取dispatch的最大等待时间  
  21.         } else {  
  22.             evutil_timerclear(&tv);  
  23.         }  
  24.   
  25.         //保存系统时间。如果有cache,将保存cache时间。  
  26.         gettime(base, &base->event_tv);  
  27.   
  28.         //之所以要在进入dispatch之前清零,是因为进入  
  29.         //dispatch后,可能会等待一段时间。cache就没有意义了。  
  30.         //如果第二个线程此时想add一个event到这个event_base里面,在  
  31.         //event_add_internal函数中会调用gettime。如果cache不清零,  
  32.         //那么将会取这个cache时间。这将取一个不准确的时间。  
  33.         clear_time_cache(base);  
  34.   
  35.         //多路IO复用函数  
  36.         res = evsel->dispatch(base, tv_p);  
  37.   
  38.         //将系统时间赋值到cache中  
  39.         update_time_cache(base);  
  40.   
  41.         //处理超时事件。参考http://blog.csdn.net/luotuo44/article/details/38637671  
  42.         timeout_process(base);  
  43.   
  44.         if (N_ACTIVE_CALLBACKS(base)) {  
  45.             int n = event_process_active(base);//处理激活event  
  46.         }   
  47.     }  
  48.   
  49.     return (retval);  
  50. }  

        可以看到,在dispatch和event_process_active之间有一个update_time_cache。而前面的gettime(base,&base->event_tv);实际上取的就是cache的时间。所以,如果该Libevent支持cache的话,会精确那么一些。一般来说,用户为event设置的回调函数,不应该执行太久的时间。这也是tv_cache时间的另外一个作用。

 

出现的bug:

        由于Libevent的解决方法并不是很精确,所以还是会有一些bug。下面给出一个bug。如果用户是在调用event_new函数之后,event_add之前对系统时间进行修改,那么无论用户设置的event超时有多长,都会马上触发超时。下面给出实际的例子。这个例子要运行在不支持monotonic时间的系统,我是在Windows运行的。
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <event2/event.h>  
  2. #include<stdio.h>  
  3.   
  4.   
  5. void timeout_cb(int fd, short event, void *arg)  
  6. {  
  7.     printf("in the timeout_cb\n");  
  8. }  
  9.   
  10.   
  11. int main()  
  12. {  
  13.     struct event_base *base = event_base_new();  
  14.   
  15.     struct event *ev = event_new(base, -1, EV_TIMEOUT, timeout_cb, NULL);  
  16.   
  17.     int ch;  
  18. //暂停,让用户有时间修改系统时间。可以将系统时间往前1个小时  
  19.     scanf("%c", &ch);   
  20.   
  21.     struct timeval tv = {100, 0};//这个超时时长要比较长。这里取100秒  
  22.     //第二个参数不能为NULL.不然也是不能触发超时的。毕竟没有时间  
  23.     event_add(ev, &tv);  
  24.   
  25.     event_base_dispatch(base);  
  26.   
  27.     return 0;  
  28. }  

        这个bug的出现是因为,在event_base_new_with_config函数中有gettime(base,&base->event_tv),所以event_tv记录了修改前的时间。而event_add是在修改系统时间后才调用的。所以event结构体的ev_timeout变量使用的是修改系统时间后的超时时间,这是正确的时间。在执行timeout_correct函数时,Libevent发现用户修改了系统时间,所以就将本来正确的ev_timeout减去了off。所以ev_timeout就变得比较修改后的系统时间小了。在后面检查超时时,就会发现该event已经超时了(实际是没有超时),就把它触发。

        如果该event有EV_PERSIST属性,那么之后的超时则会是正确的。这个留给读者去分析吧。

 

        另外,Libevent并没有考虑把时钟往后调,比如现在是9点,用户把系统时间调成10点。上面的代码如果用户是在event_add之后修改系统时间,就能发现这个bug。
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 小孩一直流黄鼻涕怎么办 咳嗽有痰 浓鼻涕怎么办 儿童鼻窦炎总反复流脓鼻涕怎么办? 宝宝感冒咳嗽流黄鼻涕怎么办 哺乳期感冒流清鼻涕怎么办 哺乳期打喷嚏流清鼻涕怎么办 哺乳期妈妈感冒流清鼻涕怎么办 哺乳期严重流清鼻涕怎么办 小孩每天都是脓鼻涕怎么办 夏天小孩咳嗽流黄脓鼻涕怎么办? 受凉了流清鼻涕怎么办 宝宝50多天鼻塞怎么办 50多天孩子咳嗽怎么办 2岁宝宝伤风鼻塞怎么办 2个月伤风鼻塞怎么办 3个月宝宝鼻塞怎么办 感冒治好后咳嗽一直不好怎么办 天气太热感冒了怎么办 3个月的婴儿鼻塞怎么办 四个月宝宝感冒鼻塞严重怎么办 4个月小孩鼻塞怎么办 4个多月的宝宝流鼻涕怎么办 4个月大的宝宝流鼻涕怎么办 两岁宝宝着凉了怎么办 7岁儿童晚上鼻塞怎么办 儿童感冒鼻塞怎么办速效办法 7岁儿童感冒鼻塞怎么办 七个月婴儿感冒流鼻涕怎么办 婴儿感冒流鼻涕怎么办速效办法 三个月婴儿感冒咳嗽流鼻涕怎么办 五个月婴儿感冒咳嗽流鼻涕怎么办 两个多月的宝宝鼻塞怎么办 3个月宝宝感冒鼻塞怎么办 2个月宝宝感冒鼻塞怎么办 5个月宝宝鼻塞怎么办 6个月宝宝鼻塞怎么办 3个月婴儿感冒咳嗽怎么办 感冒打喷嚏打不出来怎么办 4岁宝宝体温37.5怎么办 8个月的宝宝鼻塞怎么办 4个月婴儿鼻塞怎么办