基础数据结构:
event_type _type; // 类型
uint64_t _index; // guid(时间戳)
uint32_t _repeat; // 重复次数
timer_callback _caller; // 回调
时间轮结构定义:
union timer_clock
{
time64_t time;
struct
{
uint32_t m5 : 10;
uint32_t m4 : 8;
uint32_t m3 : 6;
uint32_t m2 : 6;
uint32_t m1 : 6;
uint32_t m0 : 6;
};
};
桶名 | 最大容纳 | 容器起始位置 |
m5 | 1024 | 0x000 |
m4 | 256 | 0x400 |
m3 | 64 | 0x500 |
m2 | 64 | 0x540 |
m1 | 64 | 0x580 |
m0 | 64 | 0x5c0 |
自内向外依次是m5、m4、m3、m2、m1、m0
认为m5是最小的桶,m0是最大的桶
Mgr数据成员:
std::vector<std::list<time64_t> > _wheelVec;
std::unordered_map<time64_t, timer_event> _timeSeq2Evt;
time64_t _timeSeconds; // 时间轮精度下的当前时间戳
insert:
当前有两个时间戳,一个是数据成员 _timeSeconds,另一个是insert的事件下次要触发的时间戳 deadline,将这两个时间转化为timer_clock类型,确定事件在时间轮里面的位置
if( t1.m0 != t2.m0 )
{
_wheelVec[0x5c0 + t1.m0].push_back(_handle);
}
else if( t1.m1 != t2.m1 )
{
_wheelVec[0x580 + t1.m1].push_back(_handle);
}
else if( t1.m2 != t2.m2 )
{
_wheelVec[0x540 + t1.m2].push_back(_handle);
}
else if( t1.m3 != t2.m3 )
{
_wheelVec[0x500 + t1.m3].push_back(_handle);
}
else if( t1.m4 != t2.m4 )
{
_wheelVec[0x400 + t1.m4].push_back(_handle);
}
else
{
_wheelVec[t1.m5].push_back(_handle);
}
_handle是一个唯一索引,也就是 _timeSeq2Evt 的 key
接下来是执行:
timer_clock now; // ... 赋值当前时间戳(以时间轮的精度)
while( _timeSeconds <= now.time )
{
timer_clock ts = { tickcount_ };
if( ts.m5 )
{
_clock_step_list( time_wheel_[ts.m5] );
}
else if( ts.m4 )
{
_clock_step_list( _wheelVec[ts.m4 + 0x400] );
}
else if( ts.m3 )
{
_clock_step_list( _wheelVec[ts.m3 + 0x500] );
}
else if( ts.m2 )
{
_clock_step_list( _wheelVec[ts.m2 + 0x540] );
}
else if( ts.m1 )
{
_clock_step_list( _wheelVec[ts.m1 + 0x580] );
}
else if( ts.m0 )
{
_clock_step_list( _wheelVec[ts.m0 + 0x5c0] );
}
_timeSeconds += 1;
}
return true;
_clock_step_list的入参是一个list的引用(后面需要clear这个list),这个函数需要做的就是检查时间,如果时间 <= _timeSeconds 就调用 _caller 执行,否则,重新insert_clock(这个操作其实就是把外层的事件move到内层去),遍历完整个list,做一个clear
存在的问题是如果测试期间修改服务器时间,定时器会循环很多次处理事件,可能引起网络的超时断链,cpu跑满等问题,可以考虑增加一个判断,如果循环超过2000次break一次,其他的等下次主循环跑到了再执行。