sylar-from-scratch----定时器模块timer

71 阅读5分钟

图片1.png 定时事件是网络程序需要处理的第三类事件,如定期检测一个客户连接的活动状态。要有组织地管理服务器众多的定时事件,使之能在预期的时间点被触发且不影响服务器主要逻辑,对服务器性能有重要的作用。采用的手段一般是使用链表、排序链表和时间轮将所有定时器串联起来。

定时器

定时是指在一段时间之后触发某段代码的机制,可以在这段代码中一次处理所有到期的定时器。linux提供的三种定时方法:

1、socket选项SO_RCVTIMEO和SO_SNDTIMEO

分别用来设置socket接收数据超时时间和发送数据超时时间,根据系统调用(send\sendmsg\recv\recvmsg\accept\connect)的返回值-1及errno来判断超时时间已到,进而决定是否开始处理定时任务。

2、SIGALRM信号

由alarm和setitimer函数设置的实时闹钟一旦超过,将触发SIGALRM信号。若设置周期T保持不变,则SIGALRM信号按照固定的频率生成。
基于升序链表的定时器
定时器成员至少包含两个成员超时时间(相对时间或绝对时间)和一个任务回调函数;有时还可能包含回调函数的参数及是否重启定时器等信息;若使用链表作为容器串联定时器,还需要指向下一个定时器的指针;若为双向链表则每个定时器还需要包含指向前一个定时器的指针成员。
其函数tick每隔一段固定时间执行一次,检测并处理到期的任务(依据是定时器的实际执行时间小于当前的系统事件)。添加定时器事件复杂度为O(n),删除定时器的时间复杂度为O(1),=,执行定时任务的时间复杂度为O(1)。

3、I/O复用系统调用的超时参数

Linux下三组I/O复用系统调用都带有超时参数,能统一处理信号和I/O事件,也能统一处理定时事件,但由于I/O复用系统调用可能在超时事件到期之前就返回(I/O事件发生),故需要不断更新定时参数以返佣剩余时间。

4、高性能定时器

时间轮

基于排序链表的定时器添加定时器的效率偏低,使用时间轮改进该问题。基于排序链表的定时器使用唯一的一条链表来管理所有定时器,所以插入操作的效率随着定时器数目的增多而降低。时间轮使用哈希表思想,将定时器散列在不同的链表上,使得每条链表上的数目都将明显少于原来的排序链表的定时器数目,插入操作基本不受定时器数目的影响。要提高定时精度,要使si值足够小;要提高执行效率,要求N值足够大。

image.png
添加定时器复杂度为O(1),删除时间复杂度为O(1),执行时间复杂度为O(n),但其tick频率也为固定频率。

时间堆

定时器思路为将所有定时器中超时时间最小的一个定时器的超时值作为tick间隔,这样每一次调用tick,超时时间最小的定时器必然到期,就可以对其进行处理;然后再次剩余的定时器中找到最小的超时时间对应的定时器,如此反复实现较为精确的的定时。
实现方式为最小堆,最小堆指每个节点的值都小于或等于其子节点的值的完全二叉树。最小的节点总是位于头节点。最小堆实现的定时器为时间堆。
添加定时器复杂度为O(lgn),删除时间复杂度为O(1),执行时间复杂度为O(1)。

sylar定时器

Timer类

sylar定时器对应Timer类,成员变量包括定时器的时间周期m_ms,是否循环执行,回调函数m_cb,实际执行时间m_next和指向TimeManager的指针,提供的定时器操作方法有cancel\reset\refresh。构造Timer时可以传入上述参数,也可以直接传入一个实际执行时间,其构造函数为private,TimerManager为其友元,可以访问private部分,因此只能通过TimeManager类来创建Timer对象,此外Timer还提供一个仿函数Comparator,用于比较Timer对象,比较的时间为实际执行时间m_next。

TimeManager类

所有Timer类都由TimerManager类管理,其实用set类型的容器管理Timer集合,对应于定时器的最小堆结构(其为排序过的元素);可以获取最近一个定时器的超时时间getNextTimer(),获取全部已经超时的定时器回调函数listExpiredCb(),并且提供了一个onTimerInsertedAtFront()方法,这是一个虚函数,由IOManager继承时实现,当新的定时器插入到Timer集合的首部时,TimerManager通过该方法来通知IOManager立刻更新当前的epoll_wait超时,还负责将测服务器是否发生了校时detextClockRollover()。
IOManager与TimerManager
IOManager继承了TimerManager类的所有方法,相当于外挂了一个定时器管理模块,为支持定时器共呢个,需要重新改造idle协程的实现,epoll_wait应根据下一个定时器的实际执行时间来设置超时参数。