一、已有的定时器接口
时空管理是计算机系统的主要任务。在时间管理中,我们经常利用定时器处理事情:比如tcp协议中利用定时器管理包超时,视频显示中利用定时器来定时显示视频帧,web服务中利用定时器来管理用户的超时。windows系统提供了SetTimer和timeSetEvent等定时器接口,linux中则提供了setitimer等接口。这些函数的接口很类似,大体上都是用户提供回调函数和超时时间向OS注册一个定时器事件,OS在超时时间到了的时候,调用用户提供的回调函数来完成用户想要做的事情。windows下的接口支持单进程中拥有多个定时器,而linux则只允许单进程拥有一个定时器,因此在linux下的单进程中要使用多个定时器,则需要自己维护管理,这是本文写作的出发点。另外,OS提供的定时器管理算法在大规模定时器的管理方面可能还不尽人意,这时候就需要用户去优化管理算法了,本文在这方面提供了一点素材。
二、一个最简单的多定时器的实现(linux版)
1、实现细节
这个实现允许用户使用多个自定义的定时器,每个自定义的定时器将周期地被触发直到其被删除。实现的主要思路是:
i)首先在初始化多定时器(init_mul_timer)时利用setitimer注册一个基本的时间单位(如1s)的定时事件;
ii)用户需要set_a_timer注册自定义定时器时,在timer_manage管理结构中记录这个定时器的回调函数和定时周期等参数;
iii)当基本的时间单位到期后(如SIGALRM信号到达时),遍历整个timer_manage,如果有自定义定时器的超时时间到了,就执行相应的回调函数,并将自定义定时器的超时时间置为最初值;否则将自定义定时器的超时时间相应地减一个基本的时间单位;
iv)用户通过del_a_timer来删除某个定时器,通 过destroy_mul_timer来删除整个多定时器。
2、代码
i) mul_timer.h
#include #define MAX_TIMER_CNT 10 typedef int timer_handle_t; typedef struct _timer_manage void (* old_sigfunc)(int); /* success, return 0; failed, return -1 */ #endif |
ii)mul_timer.c
static struct _timer_manage timer_manage; static void sig_func(int signo); /* success, return 0; failed, return -1 */ /* success, return 0; failed, return -1 */
ret = setitimer(ITIMER_REAL, /* success, return timer handle(>=0); failed, return -1 */ /* success, return 0; failed, return -1 */ static void sig_func(int signo) #define _MUL_TIMER_MAIN #ifdef _MUL_TIMER_MAIN static void get_format_time(char
timer_handle_t hdl[3], call_cnt int timer_proc2(void int main(void) #endif |
3、缺陷
i)新建定时器、遍历定时器和删除定时器(查找哪个定时器超时)时时间复杂度都为O(n)(n是定时器的个数);
ii)适用环境是单线程环境,如要用于多线程,需添加同步操作。
iii)程序中有些小bug,如对新建超时时间为0的定时器没有妥善的处理。
三、多定时器的改进版
1、思路
改进定时器的实现,即是改善二种所指出的几个缺陷,如下是一个改进版,主要是将遍历超时时间的时间复杂度降为了O(1).
改善思路:各定时器以一个链表的形式组织起来,除链表头定时器的超时时间是用绝对时间纪录的外,其它定时器的超时时间均用相对时间(即超时时间-前一个定时器的超时时间)纪录.
注意,各定时器都是一次性的,当定时器的超时被处理后,定时器将被自动删除.另外如果将定时器的结点改为双向结构,可以将删除定时器的时间复杂度降为O(1).
2、数据结构
每个定时器都有一个唯一的ID,这个ID是如下的结构体:
|
ptr纪录的是定时器结点的地址,entry_id则是一个自多定时器初始化后自增的id.ptr和entry_id一起组成定时器结点的key,一方面使得新建定时器时生成key的过程大为简化,另一方面使得删除定时器的时间复杂度降为O(1)(前提是定时器结点采用双向结构)。
定时器结点的数据结构如下:
|
其中的is_use是用来防止这样一种情况:用户在回调函数中调用kill_timer来删除定时器,这个时候kill_timer和遍历定时器中都有删除结点的操作,有可能将整个链表搞混乱。所以在调用用户的回调函数前先将is_use置1,在kill_timer中需检查is_use,只有在 is_use为0的情况下,才执行清理定时器结点的操作。
3、代码(windows版)
i)mul_timer.h
#ifndef _MUL_TIMER_H_ typedef struct _timer_handle /* timer entry */ typedef struct _mul_timer_manage struct _mul_timer_manage int is_valid_time_hdl(timer_handle_t hdl); #endif |
ii)mul_timer.c
void CALLBACK traverse_mul_timer(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, struct _mul_timer_manage
timer_handle_t set_timer(struct _mul_timer_manage |