定时器

定时器的概念

程序里的定时器主要实现的功能是在未来的某个时间点执行相应的逻辑

interval:间隔时间,即定时器需要在interval时间后执行
StartTimer:添加一个定时器任务
StopTimer:结束一个定时器任务
PerTickBookkeeping: 检查定时器系统中,是否有定时器实例已经到期,相当于定义了最小时间粒度。

目前常用的几种定时器结构如下:

时间轮
最小堆
链表

他们各自对于不同事件的处理的时间复杂度如下

链表的定时器实现

链表的设计上比较简单

新增

遍历链表,找到合适的位置将定时事件插入

检查

递归检查头结点,如果到了时间就执行定时事件

最小堆的实现

可以使用c++现成的multimap来实现

众所周知红黑树的插入时间复杂度是logN,而检查事件也只需要定时检查堆顶事件就可以得到定时事件
那么红黑树删除的复杂度同样为logN,和上面的不符,那么是如何实现O1的删除复杂度的呢?

  1. 延迟删除:延迟删除意味着在堆中不会立即删除到期的定时器,而是标记为无效或删除标记。
    只有当堆顶的定时器到期时间大于当前时间时,才会执行删除操作。这样可以保证删除操作的时间复杂度是 O(1)。
    定时器到期时,如果堆顶的定时器已经被标记为无效,则丢弃它并继续执行下一个到期的定时器。

  2. 惰性删除:惰性删除是指在定时器到期时,不会立即删除它,而是将其移动到堆的末尾,并更新堆的大小。
    这样一来,堆中的定时器仍然保持有序,但已经到期的定时器位于堆的末尾,可以通过减小堆的大小来删除它们。
    由于删除操作只是减小堆的大小,因此时间复杂度为 O(1)。

时间轮的实现

实现方式是数组+链表

简单时间轮:

一个齿轮,每个齿轮保存一个超时的node链表。一个齿轮表示一个时间刻度,比如钟表里面一小格代表一秒,
钟表的秒针每次跳一格。假设一个刻度代表10ms,则2^32 个格子可表示1.36年,2^16个格子可表示10.9分钟。
当要表示的时间范围较大时,空间复杂度会大幅增加。

使用场景

在任务量小的场景下:最小堆实现,可以根据堆顶设置超时时间,数组存储结构,节省内存消耗,使用最小堆可以得到比较好的效果。
而时间轮定时器,由于需要维护一个线程用来拨动指针,且需要开辟一个bucket数组,消耗内存大,使用时间轮会较为浪费资源。

在任务量大的场景下:最小堆的插入复杂度是O(lgN), 相比时间轮O(1) 会造成性能下降。更适合使用时间轮实现。

在业界,服务治理的心跳检测等功能需要维护大量的链接心跳,因此时间轮是首选。

posted @ 2024-02-27 20:47  LiviaYu  阅读(45)  评论(0)    收藏  举报