定时器:
目前主流的定时器实现包括时间轮和最小堆,最后一战是用最小堆(优先队列)实现的定时器,下面先分析下其具体实现:
typedef std::function<void(int64_t, int64_t)> HeartbeatCallback;
typedef int64_t TimeKey;
class CBattleTimer
{
public:
CBattleTimer(void);
~CBattleTimer(void);
//服务器启动之初调用该函数初始化m_InitTime作为相对时间的对照标准
void SetInitTime(ptime& time){m_InitTime = time;}
//添加定时器 参数:回调函数,定时时间,是否延续
int64_t AddTimer(HeartbeatCallback pHeartbeatCallback, int64_t interval, bool ifPersist);
void RemoveTimer(int64_t timerID){m_InvalidTimerSet.insert(timerID);}
//获取最近超时时间
bool GetNearestWaitTime(ptime& time);
//每个tick中调用run()函数尝试处理当前超时的定时器
void Run();
bool IsEmpty()const{return m_ToAddTimer.empty() && m_ThreadTimerQueue.empty();}
private:
//为每个定时器分配唯一id,用于索引查找定时器
int64_t GetTimerSequence(){return ++m_TimerSeq;}
//定时器数据结构
struct ThreadTimer{
TimeKey nextexpiredTime;//下次超时时间
TimeKey lastHandleTime;//上次超时时间
int64_t sequence;//唯一id
HeartbeatCallback pHeartbeatCallback;//回调函数
int64_t interval;//定时时间
bool ifPersist;//是否持续
ThreadTimer(TimeKey& nextexpiredTime, TimeKey& lastHandleTime, HeartbeatCallback pHeartbeatCallback, int64_t interval, int64_t sequence, bool ifPersist)
:nextexpiredTime(nextexpiredTime), lastHandleTime(lastHandleTime), pHeartbeatCallback(pHeartbeatCallback), interval(interval), sequence(sequence), ifPersist(ifPersist){}
//重载<运算符,用于优先队列中排序
bool operator <(const ThreadTimer& sThreadTimer) const{
if (nextexpiredTime != sThreadTimer.nextexpiredTime){
return nextexpiredTime > sThreadTimer.nextexpiredTime;
}
return sequence > sThreadTimer.sequence;
}
};
//获取目前时间与m_InitTime的相对时间,精确到微秒
TimeKey GetInternalTime();
std::priority_queue<ThreadTimer> m_ThreadTimerQueue;//优先队列,队列顶部永远都是最近要超时的定时器
vector<ThreadTimer> m_ToAddTimer;//添加定时器的临时队列
vector<ThreadTimer> m_PendingTimer;//将要执行的定时器临时队列
set<int64_t> m_InvalidTimerSet;//已经删除的定时器集合
int64_t m_TimerSeq;//唯一id生成
ptime m_InitTime;//相对时间对照点
};
服务器在每个tick中调用 run() 函数将添加定时器的临时队列 m_ToAddTimer 中的数据添加到优先队列 m_ThreadTimerQueue中,然后从优先队列头部遍历,去除m_InvalidTimerSet所有已经删除的定时器,然后找出所有需要执行的已经超时的定时器,放到执行临时队列 m_PendingTimer 执行回调函数。
优点:
- 实现简单,基于优先队列对定时器进行排序,简单高效。
缺点:
- 1.仅用于玩家在线时的定时功能,玩家下线时删除。无法作为玩家离线定时进行存档。
- 2.轻量级的定时器,数据量对效率有很大影响。
- 3.测试不方便,在线更改时间对定时器没有影响,这在真实生产环境下测试将非常不方便。
改进:
- 1.为每个定时器生成全局唯一guid,同时将定时器分为需要存档和不需要存档两类,将需要存档的定时器在玩家下线或服务器关闭时存档,在玩家上线或者服务器重启后加载数据构建定时器。
- 2.数据量大时可以选用时间轮定时器(下文介绍)
- 3.对游戏中的相对时间支持gm命令修改,方便实际生产环境的测试。
时间轮定时器:时间轮定时器介绍传送门
优点:
- 重量级的定时器,可承载较大的数据量。可用于服务器底层网络连接中剔除超时网络连接。
缺点:
- 1.实现复杂,特别是多级时间轮定时器。
- 2.更改时间对时间轮影响大,数据量大时耗时太久。