前言
最近学习了《高性能》定时器那章,而且看的Libco,Redis源码中都或多或少的需要处理定时事件,所以感觉写写Demo,记录一下避免遗忘。
目前想法是这个系列将分4篇,分别是按照Redis服务端处理定时事件,使用链表处理,使用时间轮处理(参照Libco实现),使用小顶堆处理。本文即为第一篇。
正文
感觉上,Redis中对于定时事件的处理并不精确,同时它的模型也最为简单,所以我就以它作为第一篇。
因为是开头,所以我们先介绍一下定时事件。
处理定时事件
在我们的服务端程序主要处理的便是两类事件,I/O事件和定时事件,当然,对于I/O事件,我们可以通过I/O多路复用较为高效的处理,而对于定时事件,如何精确地定时,组织事件和处理事件便是需要我们考虑的了。
Redis的定时事件
对于Redis定时事件的处理,在我之前的博客中有较为详细的分析,如果你对这部分并不清楚,请参照说说Redis的服务端设计,将Redis的处理流程理清。
简单来说,Redis定时事件实现(以epoll实现为例)如下:
定义一个定时器,维护一条定时器链表。
每个定时器包括了超时时间和回调函数,在链表中按照超时时间升序排列。
每次主循环,首先获取链表中最小超时时间t,也就是说t时间后将处理满足条件定时事件,然后传入epoll_wait。
epoll_wait返回后会有两种情况:
1.t未到便发生了I/O事件。
2.t到了无I/O事件发生。
返回后先处理I/O事件。然后再处理定时事件。
由于情况1我们会花费时间来处理I/O事件,所以我们处理定时事件的流程是先获取系统时间,然后遍历链表处理符合条件的事件(时间到了)。
我自己也用C++大概模拟了这个过程。(完整代码见我的github)
/*回调函数*/
static int func(void *arg)
{
std::cout << "ding dong! Now is "<< time(NULL) << std::endl;
return 1;
}
int main(void)
{
/*定时事件链表,使用类模板实现*/
TimerList<Timer> timerlist(5);
Timer t,b;
t.timeout = time(NULL) + 10; // 10s后到时
t.doJob = func; // 设置回调
b.timeout = t.timeout + 5; //15s后到时
b.doJob = func;
int timeout;
/*初始化server*/
Network server(5473);
server.Listen();
server.initMainLoop();
/*添加定时事件*/
timerlist.addEvent(t);
timerlist.addEvent(b);
while(!timerlist.isEmpty()){ // 如果队列不为空
timeout = (timerlist.getLeastTimeout() - time(NULL) ) * 1000;// 获得最小超时事件
server.startMainLoop(timeout);//内部封装epoll_wait
timerlist.dealEvent();//处理到时事件,处理完成删除事件
}
}