定时器从STL的map实现,改为最小堆的实现,主要基于以下几个方面的思考:
之前的定时器实现:
业务层需要一个定时任务的时候,底层引擎会生成一个定时器对象,同时分配一个定时器id(timerId), timerId是一个全局自增的long long值, 这个timerId会传回给脚本层持有。
底层通过map建立timerId与timerObj的映射关系,这样当在底层检测到某个timerObj是需要被触发的时候(timerObj的时间戳与现在的时间戳相等),就会将这个timerId返回给业务层,让业务层触发回调函数。
存在的问题:
1 先生成的timerObi不一定是需要先触发的。但是引擎层的Map<timerId, timerObj>的设计使得查找当前需要被触发的timerObj时,总是需要去遍历这个map(timerId最小的总是在最先被遍历到,但这个timerObj的时间戳也许是几天后的任务)
2 循环定时任务执行一次以后,timerObj的timerId是不会被重新赋值,但是它的任务仍然是往后推迟一个触发周期。任然会出现上面那个问题,即:timerId小的timerObj可能会比 timerId大的timerObj后执行。
解决方案:
1 建立一个map<timerObk.TriggerTime, timerId>, 这样这个map总是会将最小TirggerTime的timeObj的timerId返回,从而避免了map<timerId, timerObj>的遍历操作。
具体实现:
反正都是取第一个元素,为了更好地表达这个思想,使用了更贴切的数据结构来表达:最小堆
使用最小堆存储上面的{timerId, timerObj.TriggerTime}这个Node,按照堆顶就是triggerTime最小的Node
定时器的添加:
1 生成一个<tiemrId, timerObj>放入到定时map中
2 将{timerId, TirggerTime} 放入heap中,然后push_heap,让新Node参与堆化
定时器的删除:
1 将待删除的timerId存储到set中,在下一个tick的时候,将pop出来的堆顶元素与之比较,相同则删除从map与set中都删除。
2 如果定时器删除的数量比较多,因为删除只是将timerId存储的到set中,并未从map中将实际的timeObj删除(只有恰好是上面1这个情况的时候,才会被删除)。所以,项目中每一个tick都会检测一次set的大小,如果超过10000就做一次清理操作。减轻需要被删除的定时仍然占据内存的负担。