关于项目中定时器优化的一些想法

发布时间 2023-04-13 16:02:02作者: 脚比路长

定时器从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就做一次清理操作。减轻需要被删除的定时仍然占据内存的负担。