免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 27743 | 回复: 16
打印 上一主题 下一主题

linux下多定时器的实现 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-07-19 17:41 |只看该作者 |倒序浏览
linux下多定时器的实现


一、已有的定时器接口
  时空管理是计算机系统的主要任务。在时间管理中,我们经常利用定时器处理事情:比如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、代码
   见附件
3、缺陷
   i)新建定时器、遍历定时器和删除定时器(查找哪个定时器超时)时时间复杂度都为O(n)(n是定时器的个数);
   ii)适用环境是单线程环境,如要用于多线程,需添加同步操作。
   iii)程序中有些小bug,如对新建超时时间为0的定时器没有妥善的处理。

三、多定时器的改进版
1、思路
   改进定时器的实现,即是改善二种所指出的几个缺陷,如下是一个改进版,主要是将遍历超时时间的时间复杂度降为了O(1).
   改善思路:各定时器以一个链表的形式组织起来,除链表头定时器的超时时间是用绝对时间纪录的外,其它定时器的超时时间均用相对时间(即超时时间-前一个定时器的超时时间)纪录.
   注意,各定时器都是一次性的,当定时器的超时被处理后,定时器将被自动删除.另外如果将定时器的结点改为双向结构,可以将删除定时器的时间复杂度降为O(1).
2、数据结构
   每个定时器都有一个唯一的ID,这个ID是如下的结构体:

typedef struct _timer_handle
{
        unsigned long ptr;
        unsigned long entry_id;
}*timer_handle_t;

   ptr纪录的是定时器结点的地址,entry_id则是一个自多定时器初始化后自增的id.ptr和entry_id一起组成定时器结点的key
,一方面使得新建定时器时生成key的过程大为简化,另一方面使得删除定时器的时间复杂度降为O(1)(前提是定时器结点采用双向结构)。
   定时器结点的数据结构如下:
   
/* timer entry */
typedef struct _mul_timer_entry
{
    char is_use; /* 0, not; 1, yes */
    struct _timer_handle handle;
    unsigned int timeout;
    unsigned int elapse; /*  */
    int (* timer_proc) (void *arg, unsigned int *arg_len); /* callback function */
    void *arg;
    unsigned int *arg_len;
    struct _mul_timer_entry *etr_next;
}mul_timer_entry_t;

   其中的is_use是用来防止这样一种情况:用户在回调函数中调用kill_timer来删除定时器,这个时候kill_timer和遍历定时器中都有删除结点的操作,有可能将整个链表搞混乱。所以在调用用户的回调函数前先将is_use置1,在kill_timer中需检查is_use,只有在is_use为0的情况下,才执行清理定时器结点的操作。
3、代码(windows版)
  见附件。
3、缺陷
i)新建定时器的时间复杂度为O(n),删除定时器的时间复杂度也为O(n)(简单地将定时器结点改为双向结构,可将复杂度降为O(1));
ii)不能用于多线程环境
四 、多定时器的工业级实现
1、time wheelz算法
   以前的BSD内核以及现在的linux内核的实现与三中所用算法相似(未实证,只是据说),据说现在的BSD内核已采用了较好的time wheelz算法。
   time wheez算法的优点:
   i)将新建定时器的时间复杂度降近似为O(1)。它根据定时器的超时值,将新定时器散列到hash桶中;
   ii)遍历检查定时器的时间复杂度也近似为O(桶大小),如果散列均匀。
   iii)删除定时器的时间复杂度近似为O(1),通过hash算法或临时存储(空间换时间的算法)。
2、time wheelz的实现
   请参考文末给出的两个论文,惭愧得很,文章我也只是稍微瞄了下,以后有用得着的时候再深究吧。
五、参考文章
1、Adam M. Costello and George Varghess, "Redesigning the BSD Callout andTimer Facilities". 1995.11,这篇文章实现了用timewheelz算法改善BSD内核的定时器算法,google一下,有免费下载;
2、George Varghess, AnthonyLauch, "Hashed and Hierarchical Timing Wheels: Efficient DataStructures for Implementing a Timer Facility". IEEE:1997.12,这个看作者有没有提供免费下载了,否则是要从IEEE那里获取了~~


[ 本帖最后由 bripengandre 于 2009-7-19 17:49 编辑 ]

多定时器的实现.rar

48.23 KB, 下载次数: 1503

评分

参与人数 1可用积分 +5 收起 理由
net_robber + 5 原创内容

查看全部评分

论坛徽章:
5
2015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:53:172015亚冠之水原三星
日期:2015-06-02 16:34:202015年亚冠纪念徽章
日期:2015-10-19 18:13:37程序设计版块每日发帖之星
日期:2015-11-08 06:20:00
2 [报告]
发表于 2009-07-19 21:41 |只看该作者
不错

论坛徽章:
0
3 [报告]
发表于 2009-07-20 03:19 |只看该作者
LZ有没有看过libevent中定时器的实现呢?就我看你的描述而言,我认为它的实现比你的好。

论坛徽章:
0
4 [报告]
发表于 2009-07-20 10:42 |只看该作者
关注

论坛徽章:
0
5 [报告]
发表于 2009-07-20 11:47 |只看该作者
不错,这是著名的APUE课后习题10.5!
我每期学生必做的作业之一。

另外:把内核的定时器算法拿到用户态意义不大,定时器基准精度无法保证。

论坛徽章:
0
6 [报告]
发表于 2009-07-20 11:57 |只看该作者
学习~~

论坛徽章:
0
7 [报告]
发表于 2009-07-20 12:43 |只看该作者
原帖由 converse 于 2009-7-20 03:19 发表
LZ有没有看过libevent中定时器的实现呢?就我看你的描述而言,我认为它的实现比你的好。

这个没有~~~,这几个代码是最近为了解决小问题用的,并没有很多其它考虑的~~

论坛徽章:
0
8 [报告]
发表于 2009-07-20 12:45 |只看该作者
原帖由 JohnBull 于 2009-7-20 11:47 发表
不错,这是著名的APUE课后习题10.5!
我每期学生必做的作业之一。

另外:把内核的定时器算法拿到用户态意义不大,定时器基准精度无法保证。

基准精度还好,一般的应用用定时信号提供基准已经差不多了,要求再高点,还能通过查询系统时间,通过时间差来触发定时器~

论坛徽章:
0
9 [报告]
发表于 2009-07-20 13:06 |只看该作者
libevent-1.2里定时器节点是采用红黑树保存, 则最先被激活的定时器位于最左叶子节点.
libevent-1.4里定时器节点改为了小根堆minheap保存, 则最先被激活的定时器位于堆顶minheap[0]

论坛徽章:
1
寅虎
日期:2014-11-30 21:25:54
10 [报告]
发表于 2009-07-20 13:15 |只看该作者
新内核已经有了 timerfd 很好用的 可以不用这种 hack 手段
还有signalfd 信号事件化 eventfd 这个没看明白 等有时间研究下
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP