一、定时器应用场景
(1)心跳检测。
(2)游戏中的技能冷却。
(3)倒计时。
(4)其他需要延迟处理的功能。
二、利用红黑树实现定时器
红黑树是绝对有序的数据结构。
在c++中,set、map、multiset、multimap使用的是红黑树管理数据。可以利用这几个类实现定时器方案,以set为例,使用C++ 14特性。
2.1、实现接口
获取当前时间的接口GetTick(),通过C++ 11时间库chrono:
(1)steady_clock:系统启动到当前的时间,可以用来计算程序运行时间。
(2)system_clock:时间戳,可以修改。
(3)high_resolution_clock:高精度版本的steady_lock。
int64_tGetTick() { autosc=chrono::time_point_cast<chrono::milliseconds>(chrono::steady_clock::now()); autotemp=chrono::duration_cast<milliseconds>(sc.time_since_epoch()); returntemp.count(); }
相同触发时间的定时任务处理方案:
(1)越后面插入的节点,插入位置放在红黑树越右侧。
(2)使用C++的set容器;内部是红黑树管理数据。
(3)定时器节点设计,使用触发时间和ID唯一标识定时节点。
structTimeNodeBase{ int64_tid; // 描述插入的先后顺序time_texpire; // 触发时间}
(4)比较仿函数,确定数据插入红黑树的位置。利用C++14的特性,find时只需要等价key比较,无需构建key对象比较。利用基类的多态特性,只需要一个比较仿函数即可。
booloperator< (constTimeNodeBase*lhd,constTimeNodeBase*rhd) { if(lhd->expire<rhd->expire) returntrue; elseif(lhd->expire>rhd->expire) returnfalse; returnlhd->id<rhd->id; }
(5)尽量减少函数对象赋值、移动等操作,提高性能。
// TimerNode 继承 TimerNodeBasestructTimerNode:publicTimerNodeBase{ // C++ 11特性,使用函数对象。降低拷贝消耗,提高效率usingCallback=std::function<void(constTimerNode&node)>; Callbackfunc; // 构造函数,只构造一次TimerNode(int64_tid,time_texpire,Callbackfunc):func(func){ this->id=id; this->expire=expire; } };
2.2、驱动定时器方式
可以采用IO时间和定时器事件在同一个线程执行的方案,利用epoll的epoll_wait()第四个参数做定时延时。
示例:
intmain() { intepfd=epoll_wait(1); epoll_eventevs[64]={0}; while(1) { intn=epoll_wait(epfd,evs,64,delaytime); inti=0; for(i=0;i<n;i++) { /*处理IO事件*/ } // 处理定时任务事件 } return0; }
2.3、示例代码
(1)创建定时器驱动,epoll_create、epoll_wait。
(2)创建timer类,实现AddTimer()、CheckTimer()、DelTimer()等接口。
(3)选择数据结构,set容器,本质使用红黑树数据结构。
(4)定义节点的结构。
demo代码:
usingnamespacestd; structTimerNodeBase{ time_texpire; int64_tid; }; // TimerNode 继承 TimerNodeBasestructTimerNode:publicTimerNodeBase{ // C++ 11特性,使用函数对象。降低拷贝消耗,提高效率usingCallback=std::function<void(constTimerNode&node)>; Callbackfunc; // 构造函数,只构造一次TimerNode(int64_tid,time_texpire,Callbackfunc):func(func){ this->id=id; this->expire=expire; } }; // 基类引用,多态特性booloperator<(constTimerNodeBase&lhd, constTimerNodeBase&rhd) { if (lhd.expire<rhd.expire) returntrue; elseif (lhd.expire>rhd.expire) returnfalse; returnlhd.id<rhd.id; } classTimer{ public: statictime_tGetTick() { /* C++ 11时间库chrono *///表示一个具体时间autosc=chrono::time_point_cast<chrono::milliseconds>(chrono::steady_clock::now()); autotmp=chrono::duration_cast<chrono::milliseconds>(sc.time_since_epoch()); returntmp.count(); } TimerNodeBaseAddTimer(time_tmsec, TimerNode::Callbackfunc) { time_texpire=GetTick() +msec; //避免拷贝、移动构造autoele=timermap.emplace(GenID(), expire, func); returnstatic_cast<TimerNodeBase>(*ele.first); } boolDelTimer(TimerNodeBase&node) { // C++ 14新特性,不在需要传一个key对象,传递一个key的等价值autoiter=timermap.find(node); if (iter!=timermap.end()) { timermap.erase(iter); returntrue; } returnfalse; } boolCheckTimer() { autoiter=timermap.begin(); if (iter!=timermap.end() &&iter->expire<=GetTick()) { iter->func(*iter); timermap.erase(iter); returntrue; } returnfalse; } time_tTimeToSleep() { autoiter=timermap.begin(); if (iter==timermap.end()) return-1;//没有定时任务,设置epoll一直阻塞。time_tdiss=iter->expire-GetTick(); returndiss>0?diss : 0; } private: staticint64_tGenID() { returngid++; } staticint64_tgid; set<TimerNode, std::less<>>timermap; }; int64_tTimer::gid=0; intmain() { intepfd=epoll_create(1); unique_ptr<Timer>timer=make_unique<Timer>(); inti=0; timer->AddTimer(1000, [&](constTimerNode&node) { cout<<Timer::GetTick() <<"node id:"<<Home|NODE.ID<<" revoked times:"<<++i<<endl; }); timer->AddTimer(1000, [&](constTimerNode&node) { cout<<Timer::GetTick() <<"node id:"<<Home|NODE.ID<<" revoked times:"<<++i<<endl; }); timer->AddTimer(3000, [&](constTimerNode&node) { cout<<Timer::GetTick() <<"node id:"<<Home|NODE.ID<<" revoked times:"<<++i<<endl; }); autonode=timer->AddTimer(2100, [&](constTimerNode&node) { cout<<Timer::GetTick() <<"node id:"<<Home|NODE.ID<<" revoked times:"<<++i<<endl; }); timer->DelTimer(node); cout<<"now time:"<<Timer::GetTick() <<endl; epoll_eventevs[EPOLL_EV_LENFTH] = { 0 }; while (1) { intnready=epoll_wait(epfd, evs, EPOLL_EV_LENFTH, timer->TimeToSleep()); for (inti=0; i<nready; i++) { /*处理IO事件*/ } // timer检测和处理while (timer->CheckTimer()); } return0; }
总结
设计一个定时器时,先确定时间精度;选择驱动定时器的方式;选择合适的数据接口(不要选择优先队列);设计定时器基本接口 { AddTimer()、DelTimer()、CheckTimer() } 和扩展接口 { Tick()等 } ;考虑相同触发时间的定时任务处理。
关注公众号《Lion 莱恩呀》随时随地学习技术。