很长一段时间里,我错误的认识了定时器。无意中,我发现了“时间轮”这个名词,让我对定时器有了新的看法。
我错误的认为,定时器只需要一个 tick 队列,按指定的时间周期遍历队列,检查 tick 倒计时满足触发条件就触发回调。
tick 定义
struct Tick {
int_t n;
func_t func;
};
定时器轮询
void Update()
{
for (auto & tick: _ticks)
{
if (Check(tick))
{
tick.func();
Remove(tick);
}
}
}
实现很简洁,但效率却出奇的慢。
假设有100个tick,依次触发时间是100~10000毫秒,也就是每一个tick的触发间隔为100毫秒。
可以想象,在头100毫秒内,不会有任何tick被触发,但是Update却傻乎乎对100个tick进行Check。
当时间达到100毫秒的时候,只有第一个 tick 达到了触发条件,但是Update依旧会对余下99个进行Check。
时间轮很好的解决了这个问题。
思路是这样的:
- 轮盘上有若干个插槽
- 把 tick 放进合适的插槽
- 每次轮询直接触发插槽里的 tick
实现这个定时器
实现一个 最小刻度为毫秒,最大刻度为天 的定时器(最长延时23点59分59秒999毫秒)
在 3点30分25秒600毫秒 处安插一个 tick
这个实现需要用到4个轮,时(24),分(60),秒(60),毫秒(1000)
总插槽数为 1144 = 24 + 60 + 60 + 1000
首先在 时(3) 安插上这个 tick,
当执行到 时(3) 的时候,删除这个 tick,检查到该 tick 还有 30分25秒600毫秒。
于是在 分(30) 安插上这个 tick,
当执行到 分(30) 的时候,删除这个 tick,检查到该 tick 还有 25秒600毫秒。
于是在 秒(25) 安插上这个 tick,
当执行到 秒(25) 的时候,删除这个tick,检查到该 tick 还有 600毫秒。
于是在 毫秒(600) 安插上这个 tick,
当执行到 毫秒(600) 的时候,删除这个tick,触发这个 tick。
这个 tick 从被安插到被触发,总共只需要 Check(4) 次。
如果采用本文开头的思路,那将会被 Check(天文数字) 次。
lua 实现
sformat = string.format
tinsert = table.insert
tremove = table.remove
tconcat = table.concat
mfloor = math.floor
local utils = require("utils")
local _M = { _slots = nil,
_cycle = nil, }
function _M.Init(self, cycle)
self._slots = {}
utils.tinsert_n(self._slots, {}, 4)
utils.tinsert_n(self._slots[1], {}, 24)
utils.tinsert_n(self._slots[2], {}, 60)
utils.tinsert_n(self._slots[3], {}, 60)
utils.tinsert_n(self._slots[4], {}, 1000)
self._cycle = cycle
end
function _M.Update(self, cycle)
local h1, m1, s1, ms1 = utils.ms2t(self._cycle)
self._cycle = cycle
local h2, m2, s2, ms2 = utils.ms2t(self._cycle)
self:__UpdateT__(24, 1, h1, h2, utils.bind(self.__UpdateH__, self))
self:__UpdateT__(60, 2, m1, m2, utils.bind(self.__UpdateM__, self))
self:__UpdateT__(60, 3, s1, s2, utils.bind(self.__UpdateS__, self))
self:__UpdateT__(1000, 4, ms1, ms2, utils.bind(self.__UpdateMS__, self))
end
function _M.AddTimer(self, delay, func)
self:__Insert__(delay + 1, func)
end
function _M.__Insert__(self, delay, func)
if 0 == delay then
func()
else
local h1, m1, s1, ms1 = utils.ms2t(delay)
local h2, m2, s2, ms2 = utils.ms2t(delay + self._cycle)
local tick = { func = func,
time = { h = h2, m = m2, s = s2, ms = ms2 } }
if h1 ~= 0 then
tinsert(self._slots[1][h2 == 0 and 24 or h2], tick)
elseif m1 ~= 0 then
tinsert(self._slots[2][m2 == 0 and 60 or m2], tick)
elseif s1 ~= 0 then
tinsert(self._slots[3][s2 == 0 and 60 or s2], tick)
elseif ms1 ~= 0 then
tinsert(self._slots[4][ms2 == 0 and 1000 or ms2], tick)
end
end
end
function _M.__UpdateT__(self, cycle, index, first, last, func)
local slots = self._slots[index]
while first ~= last do
first = first + 1
for i = 1, #slots[first] do
func(slots[first][i])
end
slots[first] = {}
first = first % cycle
end
end
function _M.__UpdateH__(self, v)
self:__Insert__(utils.t2ms(0, v.time.m, v.time.s, v.time.ms), v.func)
end
function _M.__UpdateM__(self, v)
self:__Insert__(utils.t2ms(0, 0, v.time.s, v.time.ms), v.func)
end
function _M.__UpdateS__(self, v)
self:__Insert__(utils.t2ms(0, 0, 0, v.time.ms), v.func)
end
function _M.__UpdateMS__(self, v)
self:__Insert__(utils.t2ms(0, 0, 0, 0), v.func)
end
return _M