定时器队列

        定时器属于基本的基础组件,不管是用户空间的程序开发,还是内核空间的程序开发,很多时候都需要有定时器作为基础组件的支持,但使用场景的不同,对定时器的实现考虑也不尽相同,本文讨论了在 Linux 环境下,应用层和内核层的定时器的各种实现方法,并分析了各种实现方法的利弊以及适宜的使用环境。

       首先,给出一个基本模型,定时器的实现,需要具备以下几个行为,这也是在后面评判各种定时器实现的一个基本模型 [1]:

       StartTimer(Interval, TimerId, ExpiryAction)

       注册一个时间间隔为 Interval 后执行 ExpiryAction 的定时器实例,其中,返回 TimerId 以区分在定时器系统中的其他定时器实例。

       StopTimer(TimerId)

       根据 TimerId 找到注册的定时器实例并执行 Stop 。

       PerTickBookkeeping()

       在一个 Tick 内,定时器系统需要执行的动作,它最主要的行为,就是检查定时器系统中,是否有定时器实例已经到期。注意,这里的 Tick 实际上已经隐含了一个时间粒度 (granularity) 的概念。

       ExpiryProcessing()

       在定时器实例到期之后,执行预先注册好的 ExpiryAction 行为。

       上面说了基本的定时器模型,但是针对实际的使用情况,又有以下 2 种基本行为的定时器:

       Single-Shot Timer

       这种定时器,从注册到终止,仅仅只执行一次。

       Repeating Timer

 

       这种定时器,在每次终止之后,会自动重新开始。本质上,可以认为 Repeating Timer 是在 Single-Shot Timer 终止之后,再次注册到定时器系统里的 Single-Shot Timer,因此,在支持 Single-Shot Timer 的基础上支持 Repeating Timer 并不算特别的复杂。

/// add 2014/3/6
       因为可能一个系统中(进程)中有很多的定时器,所以就有了定时器队列的概念,用什么数据结构存储定时器可能影响到系统的性能,对于定时器的操作复杂度和数据结构上的复杂度很相似,StartTimer相当于插入操作, StopTimer与一般理解上的StopTimer有点不同,这里的StopTimer是找出最先到时间的定时器(相当于找到最小值),而不是找到特定的值, PerTickBookkeeping与StopTimer意思差不多,复杂度一样。在这篇文章中(http://www.ibm.com/developerworks/cn/linux/l-cn-timers/)介绍了一种“时间轮”的算法,其实和大数据排序的bitMap方法原理一样,以空间换取速度。所以考虑到数据规模,也就是定时器的规模,一般来说使用堆的时间复杂度更低一些,但是如果定时器很少,起始对于目前的系统的速度来说,没有多大规模。对于如何实现定时器队列,在ace中有了很好的封装。

       此外,除了定时器复杂度,还有一个精度的问题,也就是“Trick”的问题,
/// add finish 2014/3/6

       在 2.4 的内核中,并没有提供 POSIX timer [ 2 ]的支持,要在进程环境中支持多个定时器,只能自己来实现,好在 Linux 提供了 setitimer(2) 的接口。它是一个具有间隔功能的定时器 (interval timer),但如果想在进程环境中支持多个计时器,不得不自己来管理所有的计时器。 setitimer(2) 的定义如下:

setitimer 的原型
#include <sys/time.h>
 int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);

       setitimer 能够在 Timer 到期之后,自动再次启动自己,因此,用它来解决 Single-Shot Timer 和 Repeating Timer 的问题显得很简单。该函数可以工作于 3 种模式:

       ITIMER_REAL 以实时时间 (real time) 递减,在到期之后发送 SIGALRM 信号

       ITIMER_VIRTUAL 仅进程在用户空间执行时递减,在到期之后发送 SIGVTALRM 信号

I       TIMER_PROF 进程在用户空间执行以及内核为该进程服务时 ( 典型如完成一个系统调用 ) 都会递减,与 ITIMER_VIRTUAL共用时可度量该应用在内核空间和用户空间的时间消耗情况,在到期之后发送 SIGPROF 信号

       setitimer不会引起线程的阻塞、也不会引起线程的切换动作,就是简单的启动一个定时器,开始定时,而且这种定时应该是基于内核的,(windwos的settimer是基于一种消息的模型);setitimer虽然有三种类型ITIMER_REAL,ITIMER_VIRTUAL ITIMER_PROF,但是在同一时间同一进程,一种类型的只能有1个setitimer。

       “在对精度要求较高的情况下使用select()作为定时器,最大的好处就是不会影响信号处理线程安全,而且精度能得到保证。当时间延时时间较长时,select和pselect表现较差,当时间小于1毫秒时,他们的精确度便提高了,表现与usleep、nanosleep不相上下,有时精度甚至超过后者,select,和sleep是可重入函数,在使用的时候会引起线程的切换;所以有不会影响信号处理线程安全,而usleep,nanosleep,不可重入函数,程序是在暂停状态,也就是不能线程切换;但是不知道setitimer会不会记时;”

/// add 2014/3/6
       对于上面的一段话简单注解,select和sleep是可重入函数,使用的时候不是“引起”线程的切换,是使用的时候cpu可能会切换线程,如果定时的时间较长,那么不准确的原因可能是线程切换造成的。
/// add 2014/3/6

参考:
http://www.ibm.com/developerworks/cn/linux/l-cn-timers/
http://www.cnblogs.com/processakai/archive/2012/04/11/2442294.html

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很高兴为您解答这个问题! 以下是用C语言编写一个定时器队列的接口函数的示例: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> // 定义定时器结构体 typedef struct _timer { int id; // 定时器ID int interval; // 定时器间隔时间 int count; // 定时器计数器 void (*callback)(void *ctx); // 回调函数 void *ctx; // 回调函数上下文 struct _timer *next; // 指向下一个定时器的指针 } timer_t; // 定义定时器队列结构体 typedef struct _timer_queue { int count; // 定时器数量 timer_t *head; // 指向第一个定时器的指针 } timer_queue_t; // 初始化定时器队列 void timer_queue_init(timer_queue_t *queue) { queue->count = 0; queue->head = NULL; } // 添加定时器 void timer_queue_add(timer_queue_t *queue, int id, int interval, void (*callback)(void *ctx), void *ctx) { // 创建新定时器 timer_t *timer = (timer_t *)malloc(sizeof(timer_t)); timer->id = id; timer->interval = interval; timer->count = interval; timer->callback = callback; timer->ctx = ctx; timer->next = NULL; // 添加到队列 if (queue->head == NULL) { queue->head = timer; } else { timer_t *tail = queue->head; while (tail->next != NULL) { tail = tail->next; } tail->next = timer; } queue->count++; } // 注销定时器 void timer_queue_remove(timer_queue_t *queue, int id) { timer_t *prev = NULL; timer_t *curr = queue->head; while (curr != NULL) { if (curr->id == id) { if (prev == NULL) { queue->head = curr->next; } else { prev->next = curr->next; } free(curr); queue->count--; break; } prev = curr; curr = curr->next; } } // 执行定时器 void timer_queue_poll(timer_queue_t *queue) { timer_t *prev = NULL; timer_t *curr = queue->head; while (curr != NULL) { curr->count--; if (curr->count == 0) { curr->callback(curr->ctx); curr->count = curr->interval; if (curr->interval <= 0) { if (prev == NULL) { queue->head = curr->next; } else { prev->next = curr->next; } timer_t *next = curr->next; free(curr); curr = next; queue->count--; continue; } } prev = curr; curr = curr->next; } } // 测试函数,打印字符串 void test_timer_callback(void *ctx) { char *str = (char *)ctx; printf("%s\n", str); } int main() { timer_queue_t queue; timer_queue_init(&queue); // 添加两个定时器 timer_queue_add(&queue, 1, 10, test_timer_callback, "This is the first timer."); timer_queue_add(&queue, 2, 20, test_timer_callback, "This is the second timer."); // 循环执行定时器 for (int i = 0; i < 100; i++) { timer_queue_poll(&queue); } // 注销定时器 timer_queue_remove(&queue, 1); timer_queue_remove(&queue, 2); return 0; } ``` 上面的示例代码实现了一个简单的定时器队列,可以添加、注销和执行多个定时器任务,并对每个任务进行不同事件的轮询执行。准确来说,它是一个基于链表的定时器实现,没有使用系统提供的定时器API函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值