简单点说,定时事件是到了一个时间点就需要提示去执行某个动作。首先想到的利用信号,一般有两组设置定时信号的函数:alarm和setitimer。
alarm是设置一次实时的延迟,单位是秒,信号是SIGALRM。相关man手册
setitimer用来设置一个间隔时间,周期性的触发信号。系统给进程提供了不同的时间,实时时间、进程运行时间、进程运行和系统支持时间。设置时候的选项和触发信号也不一样。实时时间设置选项和触发信号分别是ITIMER_REAL和SIGALRM。相关man手册
定时事件是服务器中需要经常处理的事件之一,比如处理非活动链接。容纳定时时间的称为定时器容器,简称定时器。一般处理定时器的数据结构有三种:升序定时器链、时间轮、时间堆。
升序定时器链是用链表组织的结构,保证每次插入之后整个定时器链仍然有序。
每次处理定时事件从链表头开始处理,直到第一个比定时时间更大的定时器出现为止。从执行效率上来看,添加定时器的时间复杂度是O(n),删除定时器的时间复杂度是O(1),执行一次处理定时事件的时间复杂度是O(1)
PS:《Linux高性能服务器编程》中认为处理定时事件的时间复杂度是O(1),我持有疑问。因为执行一次处理有可能遍历整个事件链,即使时间分布均匀分散,每次处理也是链长度的几分之一,与n有关。
时间轮是一种分层模型,应用了哈希的思想。先直观上来看:
每一个定时器都被散列到一个轮槽上面。添加定时器时如何计算槽号:
散列的槽号=当前槽号+(定时度过时长%一圈时长)/一槽的时长
每一个槽对应的是一个定时器链,执行定时事件就是遍历当前槽号对应的定时器链上的定时器,看是否超过定时时间。每一次执行定时事件之后就看当前累积时间是否超过一槽的时长,如果超过就需要将指针移动到下一个槽号。时间轮的效率很高,添加一个定时器的时间复杂度是O(1),删除一个定时器的时间复杂度也是O(1),执行一次定时事件的时间复杂度是O(n)。但因为所有定时器被散列到不同的槽上面,所以实际上的执行效率比O(n)要好得多。
时间轮也非常易于扩展,当有多个精度或者时间跨度很大的时候,能够有多个不同粒度的时间轮。相邻两个时间轮,高精度的转动一圈,低精度的移动一槽,像老式的机械水表一样。
时间堆是另外一种设计思路:将所有定是其中超时时间最小的作为定时间隔。每次执行定时事件处理都会处理到那个超时时间最小的定时器,同时选出剩下的定时器中选出超时时间最小的,设置这个时间为下一次的定时时间。因为需要经常执行寻找最小,所以用最小堆来管理所有的定时器。
时间堆可以实现较为精确的定时,添加定时器的时间复杂度是O(logn),删除和执行定时器的时间复杂度是O(1)。
参考资料
《Linux高性能服务器编程》
http://www.cnblogs.com/zhongwencool/p/timing_wheel.html
http://www.ibm.com/developerworks/cn/linux/l-cn-timers/