时间轮算法简述
基本结构
Hash表嵌套队列,管理不同Tick时间调度的程序出队列,实现不同间隔Tick的程序抢占,从而实现游戏中定时检测等功能的实现
- Hash表嵌套队列 :hash表的key值表示时间轮片的位置,队列表示同一Tick位置的线程排队情况;
- HashCode 的计算:1- 从当前的Tick位置开始 加上 线程设置的定时Tick 2- 将得到的Tick数,与时间轮的长度进行求余数操作,得到时间轮上的Tick位置 3- 将该线程插入到该Tick位置的线程队列中
- 时间轮的工作流程:
1- 从进程开始的时候,设置起始位置Tick(初始化的时候,一般都是从Tick==0开始计算)
//第一个tick时初始化m_tvLastTick
if (0 == m_tvLastTick.tv_sec && 0 == m_tvLastTick.tv_usec)
{
//最后一次系统tick时间
m_tvLastTick = tvNow;
}
//当前的时间和上一次tick的时间,获取当前所在的时间轮位置
uint32_t ticks = GetTicks(tvNow, m_tvLastTick); //从系统执行程序开始初始化Tick
2- 添加执行任务的线程到时间轮队列中, HashCode的计算,主要是将MS转化为时间轮的Tick数
- 按照线程的需要,多少毫秒Tick一次线程,在时间轮的队列中具体的任务线程poTimer中设置过期时间:ExpiredTime = 当前Tick数+(任务线程的Tick时间转化的Tick数)
- MsToTick: (uiMs - 1) / TICK_INTERVAL + 1; 时间轮转动的速度,即:设计的每个Tick之间的时间间隔TICK_INTERVAL
- 求余数 和 按位与&的操作 :
- 按位与 :通常用来取某个数的低N位的数值,当 x = 2^n时, a % x == a & (x -1),所以设计上,TICK_INTERVAL设计为2的幂次
3- 进程执行过程中,
1-根据当前的时间戳,计算当前处于时间轮片的那个Tick位置
2-获取当前Tick位置的队列,遍历队列中** ExpireTime** 到期的线程,按照FIFO的原则出队列
3-如果该任务需要继续Tick执行,则作为新的计算对象,从当前的进程Tick开始,重新计算HashCode,重新加入到队列中
相关代码
//基类:Hash表和队列的工作流程
/*
1-哈希表嵌套队列
typedef std::list<CBaseTimer*> TimerList;
TimerList m_aoTimerList[TIMER_LIST_NR];
2-将Tick程序添加在队列中:CBaseTimerMgr::AddTimer
*/
//时间单位转化为微秒
static inline int TimevalInterval(const struct timeval& tv1, const struct timeval& tv2)
{
//秒tv_sec转化为微秒 毫秒tv_usec转为微秒
return (tv1.tv_sec - tv2.tv_sec) * MILLISEC_PER_SEC
+ (tv1.tv_usec - tv2.tv_usec) / MICROSEC_PER_MS;
}
//微秒转化为以tick为单位的数值
static inline uint32_t MsToTick(uint32_t uiMs)
{
//每个tick的时间长度TICK_INTERVAL
return (uiMs - 1) / TICK_INTERVAL + 1;
}
//获取两个时间之间的tick数
static inline uint32_t GetTicks(const struct timeval& tv1, const struct timeval& tv2)
{
int uiMs = TimevalInterval(tv1, tv2);
if (uiMs <= 0)
{
return 0;
}
return MsToTick(uiMs);
}
static inline uint32_t HashCode(uint32_t uiKey)
{
return uiKey & TIMER_LIST_MASK;
}
int CBaseTimerMgr::UpdateLastTick(uint32_t uiTicks)
{
if (0 == uiTicks)
{
return 0;
}
struct timeval tv;
tv.tv_sec = uiTicks * TICK_INTERVAL / MILLISEC_PER_SEC;
tv.tv_usec = (uiTicks * TICK_INTERVAL - tv.tv_sec * MILLISEC_PER_SEC)
* MICROSEC_PER_MS;
timeradd(&m_tvLastTick, &tv, &m_tvLastTick);
return 0;
}
int CBaseTimerMgr::AddTimer(CBaseTimer *poTimer)
{
CHECK_IF_NULL_RET(poTimer, -1);
uint32_t uiTicks = MsToTick(poTimer->GetIntervalTime()); //pass
if (0 == uiTicks)
{
LOG_ERR("add timer [%s] failed, tick=0", poTimer->GetName().c_str());
return -1;
}
uint32_t uiExpire = m_uiTickCount + uiTicks;
poTimer->SetExpireTick(uiExpire);
uint32_t uHashCode = HashCode(uiExpire);
m_aoTimerList[uHashCode].push_back(poTimer);
return 0;
}
int CBaseTimerMgr::RunTimer(uint32_t uiTick)
{
TimerList& list = m_aoTimerList[HashCode(uiTick)];
TimerList::iterator iter = list.begin();
while(iter != list.end())
{
CBaseTimer *timer = *iter;
if (uiTick != timer->GetExpireTick()) //pass
{
++iter;
continue;
}
iter = list.erase(iter);
STAT_FUNC_TIME_TICK(timer->m_szTimerName.c_str());
timer->OnTick();
if (timer->GetCycleFlag())
{
AddTimer(timer);
continue;
}
}
return 0;
}
int CBaseTimerMgr::TimerTrigger ()
{
struct timeval tvNow;
gettimeofday(&tvNow, NULL);
//第一个tick时初始化m_tvLastTick
if (0 == m_tvLastTick.tv_sec && 0 == m_tvLastTick.tv_usec)
{
//最后一次系统tick时间
m_tvLastTick = tvNow;
}
//当前的时间和上一次tick的时间,获取当前所在的时间轮位置
uint32_t ticks = GetTicks(tvNow, m_tvLastTick);
uint32_t uiLastTick = m_uiTickCount;
m_uiTickCount += ticks;
for( uint32_t i = 1; i <= ticks; ++i)
{
//出队列操作
RunTimer(uiLastTick + i);
}
UpdateLastTick(ticks);
// if (NULL != m_pBaseMonitor)
// {
// m_pBaseMonitor->TickLog();
// }
return 0;
}
int CBaseTimerMgr::DelTimer(const CBaseTimer *poTimer)
{
int iCount = 0;
for (int i = 0; i < TIMER_LIST_NR; ++i)
{
TimerList& list = m_aoTimerList[i];
TimerList::iterator iter = list.begin();
while(iter != list.end())
{
if (poTimer == *iter)
{
iter = list.erase(iter);
iCount++;
}
else
{
++iter;
}
}
}
return iCount;
}
//声明自定义的任务线程
项目中声明注册定时器
/**
* 注册TICK的接口
* _tick tickname
* _class _obj的类名
* _obj 对象
* _func tick触发函数
* _name tick name 用于统计
* _interval 超时时长单位毫秒
*/
#define DECLARE_TICK(_tick, _class, _obj, _func, _name, _interval) \
static CAppTick<_class> *_tick = NULL; \
if (NULL == _tick) \
{ \
_tick = new CAppTick<_class>; \
_tick->SetApp(_obj); \
_tick->SetFunc(_func); \
_tick->SetName(_name); \
_tick->SetCycleFlag(true); \
_tick->SetIntervalTime(_interval); \
CBaseTimerMgr::Instance().AddTimer(_tick); \
}
模板注册定时器执行函数
template<class _CApp>
class CAppTick : public CBaseTimer
{
public:
//声明了函数指针;(_CApp::*_Func)() 无参数类型
typedef int (_CApp::*_Func)();
CAppTick() :
m_pApp(NULL),
m_pFunc(NULL)
{
}
/**
* @brief 构造函数
* @param app 对应的App
* @param szName 定时器的名字
*/
CAppTick(_CApp *pApp, _Func pFunc, const char *szName, uint32_t uiInterval):
CBaseTimer(szName, uiInterval, true),
m_pApp(pApp),
m_pFunc(pFunc)
{
}
void SetApp(_CApp *pApp)
{
m_pApp = pApp;
}
void SetFunc(_Func pFunc)
{
m_pFunc = pFunc;
}
/**
* @brief 超时处理函数
* @return 0 if success
*/
//执行的是对应线程中的tick函数
virtual int OnTick()
{
if (NULL != m_pApp && NULL != m_pFunc)
{
return (m_pApp->*m_pFunc)();
}
return 0;
}
private:
_CApp *m_pApp;
_Func m_pFunc;
};
#endif
/