版本比较
标识
- 该行被添加。
- 该行被删除。
- 格式已经改变。
...
基于epoll超时实现定时器功能,精度毫秒级,支持在指定超时时间结束之后执行回调函数。本章对应源码:https://github.com/zhongluqiang/sylar-from-scratch/releases/tag/v1.7.0。
《Linux高性能服务器编程.pdf》第11章对定时器有完整而详细地介绍,包括原理与代码实现,建议先阅读这章之后再来看sylar的定时器模块,以下关于定时器概述的内容基本直接摘抄于这章。
定时器概述
...
通过定时器可以实现给服务器注册定时事件,这是服务器上经常要处理的一类事件,比如3秒后关闭一个连接,或是定期检测一个客户端的连接状态。
定时事件依赖于Linux提供的定时机制,它是驱动定时事件的原动力,目前Linux提供了以下几种可供程序利用的定时机制:
1. alarm()或setitimer(),这俩的本质都是先设置一个超时时间,然后等SIGALARM信号触发,通过捕获信号来判断超时
2. 套接字超时选项,对应SO_RECVTIMEO和SO_SNDTIMEO,通过errno来判断超时
3. 多路复用超时参数,select/poll/epoll都支持设置超时参数,通过判断返回值为0来判断超时
4. timer_create系统接口,实质也是借助信号,参考man 2 timer_create
5. timerfd_create系列接口,通过文件描述符的形式操作定时器,可配合IO多路复用,参考man 2 timerfd_create
服务器程序通常需要处理众多定时事件,如何有效地组织与管理这些定时事件对服务器的性能至关重要。为此,我们要将每个定时事件分别封装成定时器,并使用某种容器类数据结构,比如链表、排序链表和时间轮,将所有定时器串联起来,以实现对定时事件的统一管理。
每个定时器通常至少包含两个成员:一个超时时间(相对时间或绝对时间)和一个任务回调函数。除此外,定时器还可以包括回调函数参数及是否自动重启等信息。
有两种高效管理定时器的容器:时间轮和时间堆,sylar使用时间堆的方式管理定时器。
几种定时器管理方式介绍
基于升序链表的定时器
所有定时器组织成双链表结构,链表成员包含超时时间,回调函数,回调函数参数,以及前后指针几项内容。
所有定时器在链表中按超时时间进行升序排列,超时时间短的在前,长的在后。每次添加定时器时,都要按超时时间将定时器插入到链表的指定位置。
程序运行后维护一个周期性触发的tick信号,比如利用alarm函数周期性触发ALARM信号,在信号处理函数中从头遍历定时器链表,发现当前定时器已超时时,记录下该定时器,然后将当前定时器删除。
执行所有超时定时器的回调函数。
以上就是一个基于升序链表的定时器管理方式,这种方式添加定时器的时间复杂度是O(n),删除定时器的时间复杂度是O(1),执行定时任务的时间复杂度是O(1)。
时间轮
时间堆
...
sylar定时器设计
sylar的定时器依赖IO协程调度模块,通过多继承的方式给IO协程调度模块增加定时器管理功能,相当于给IOManager外挂了一个定时器管理器。
sylar的定时器基于epoll_wait超时实现,精度只支持毫秒级,因为epoll_wait的超时精度也只有毫秒级。
sylar的定时器包括一次性定时器,循环定时器,条件定时器。
大体思路是创建定时器时指定超时时间和回调函数,然后以当前时间加上超时时间计算出超时的绝对时间点,然后所有的定时器按这个超时时间点排序,从最早的时间点开始取出超时时间作为idle协程的epoll_wait超时时间,epoll_wait超时结束时把所有已超时的定时器收集起来,执行它们的回调函数。
sylar定时器实现
sylar的定时器模块整体比较简单,只需要维护好所有的定时。
...
一点讨论
sylar的定时器以gettimeofday()来获取绝对时间点并判断超时,所以依赖系统时间,如果系统进行了校时,比如NTP时间同步,那这套定时机制就失效了。sylar的解决办法是设置一个较小的超时步长,比如3秒钟,也就是epoll_wait最多3秒超时,如果最近一个定时器的超时时间是10秒以后,那epoll_wait需要超时3次才会触发。每次超时之后除了要检查有没有要触发的定时器,还顺便检查一下系统时间有没有被往回调。如果系统时间往回调了1个小时以上,那就触发全部定时器。个人感觉这个办法有些粗糙,其实只需要换个时间源就可以解决校时问题,换成clock_gettime(CLOCK_MONOTONIC_RAW)的方式获取系统的单调时间,就可以解决这个问题了。
目录 |
---|