8
一个基于最小堆的定时器实现
source link: https://zhuanlan.zhihu.com/p/365061938
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
一个基于最小堆的定时器实现
在以前的文章中,用C写过一个时间轮算法的定时器,这次我用Lua写了一个基于最小堆的定时器。
代码加上注释只有一百多行,它的核心算法只有两个:
- 上移:当前结点比父结点小时,要和父结点交换位置,如此重复一直找到合适的位置。
- 下移:当前结点比子结点大时,要和子结点交换位置,如此重复一直找到合适的位置。
使用最小堆的定时器效率也是非常高的:
- 增加结点:把结点加到数组最后,通过“上移”找到合适的位置,时间复杂度是O(logN)。
- 删除结点:取出最后的结点,替换掉要删除的结点;然后对替换的结点作“上移”或下移“,时间复杂度也是O(logN)。
- 每个 Tick:取根结点比较当前时间:
- 如果根结点的时间大于当前时间,那么后面的就不用比较了,时间复杂度是O(1)。
- 反之说明根结点超时,触发根结点的回调;然后对根结点执行删除操作,时间复杂度是O(logN)。
从上面的描述可知,最小堆定时器的总体时间复杂度是O(logN),也由于它的算法相对简单,很容易理解;因此有很多库使用该算法来实现定时器。
下面是实现代码:
---
--- 一个基于最小堆的定时器实现
--- Created by colin.
---
local setmetatable = setmetatable
local tunpack = table.unpack
local xpcall = xpcall
local traceback = debug.traceback
---@class TimerNode 时间结点
---@field time number 超时的时间点
---@field period number 周期触发的间隔,为0表示没有周期触发
---@field callback fun(node: TimerNode, ...) 超时回调
---@field args any[] 回调函数的参数,在schedule可以传入自定义参数
---@field index number 跟踪当前结点的堆索引
---@class Timer 定时器
---@field heap TimerNode[]
local MHTimer = {}
local TimerMT = {
__index = MHTimer,
__len = function(t)
return #t.heap
end,
}
---向上移动结点
---@param heap TimerNode[] 最小堆
---@param index number 开始比较的索引
---@param node TimerNode 将占坑的结点
local function _heap_shift_up(heap, index, node)
local parent = index // 2
while parent >= 1 and heap[parent].time > node.time do
heap[index] = heap[parent]
heap[index].index = index
index = parent
parent = index // 2
end
heap[index] = node
heap[index].index = index
end
---向下移动结点
---@param heap TimerNode[] 最小堆
---@param index number 开始比较的索引
---@param node TimerNode 将占坑的结点
local function _heap_shift_down(heap, index, node)
local size = #heap + 1
local child = index * 2 + 1
while child <= size do
if child == size or heap[child].time > heap[child-1].time then
child = child - 1
end -- 取值较小的孩子比较
if node.time > heap[child].time then
heap[index] = heap[child]
heap[index].index = index
index = child
child = index * 2 + 1
else
break
end
end
heap[index] = node
heap[index].index = index
end
---增加最小堆元素
---@param heap TimerNode[]
---@param node TimerNode
local function _heap_add(heap, node)
_heap_shift_up(heap, #heap + 1, node)
end
---删除最小堆元素
---@param heap TimerNode[]
---@param node TimerNode
local function _heap_remove(heap, node)
if node.index > 0 then
local sz = #heap
local lastnode = heap[sz]
heap[sz] = nil
if node ~= lastnode then
local parent = node.index // 2
if parent >= 1 and heap[parent].time > lastnode.time then
_heap_shift_up(heap, node.index, lastnode)
else
_heap_shift_down(heap, node.index, lastnode)
end
end
node.index = 0
end
end
---调整元素的位置, 由于元素的时间点变化,要将其调整至合适的位置
---@param heap TimerNode[]
---@param node TimerNode
local function _head_adjust(heap, node)
if node.index > 0 then
local parent = node.index // 2
if parent >= 1 and heap[parent].time > node.time then
_heap_shift_up(heap, node.index, node)
else
_heap_shift_down(heap, node.index, node)
end
end
end
---创建一个Timer
function MHTimer.new()
return setmetatable({
heap = {},
}, TimerMT)
end
---更新时间点,触发超时事件,外部要不断调用该函数,并传入当前时间点
---@param time number 时间点
function MHTimer:tick(time)
local heap = self.heap
---@type TimerNode
local node
while #heap > 0 do
node = heap[1]
if time >= node.time then -- timeout
if node.callback then
local ok, err = xpcall(node.callback, traceback, node, tunpack(node.args))
if not ok then
print(err)
end
end
if node.period > 0 then
node.time = node.time + node.period
_head_adjust(heap, node)
else
_heap_remove(heap, node)
end
else
break
end
end
end
---增加调度事件
---@param first number 首次发生的时间点,注意不是间隔
---@param period number 周期性触发的间隔,如果为0表示没有周期性触发
---@param callback fun(node: TimerNode, ...) 触发时的回调函数,以及附加的任意参数
---@return TimerNode 返回结点
function MHTimer:schedule(first, period, callback, ...)
---@type TimerNode
local node = {
time = first,
period = period or 0,
callback = callback,
args = {...},
index = 0,
}
_heap_add(self.heap, node)
return node
end
---删除调试事件
---@param node TimerNode
function MHTimer:unschedule(node)
_heap_remove(self.heap, node)
end
return MHTimer
该定时器收录在我的Lua扩展库中,也提供了一些简单的测试代码。
该库目前正在不断完善,并且我希望它是一个经过测试的稳定高效的库,特别适用于游戏服务器的场景。
视我的时间和精力,后面会慢慢加入更多的模块,欢迎关注:
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK