必须用 std::vector,因其内存连续、支持 O(1) 取模索引;定时器对象存池中,槽内仅存 ID;addTimer 须计算相对延迟后取整定位槽位;执行前需先拷贝待触发 ID 再清空槽;多级时间轮通过降级与重散列兼顾精度与内存效率。

时间轮的核心结构用 std::vector 还是 std::deque?
必须用 std::vector,且大小固定(如 256 或 65536)。时间轮本质是环形数组,靠取模索引访问槽位,std::deque 的迭代器不保证连续、随机访问性能差,且无法高效做 slot_index = time % wheel_size 这类计算。std::vector 支持 O(1) 索引,内存连续,缓存友好。注意:不要用 std::vector<:list>> 存任务链表——频繁分配小节点会触发大量堆操作;改用 std::vector<:vector>>,把定时器对象统一存在一个 std::vector 池里,只在槽中存 ID(整数索引),避免指针失效和内存碎片。
addTimer() 中如何避免重复插入和精度丢失?
关键不是“加进去”,而是“算对它该落在哪个槽”。假设时间轮周期为 T(比如 60 秒),分 N 格,则每格代表 T/N 时间。传入的绝对到期时间 expire_time 必须先减去当前时间得到相对延迟 delay,再做:
-
slot = (current_tick + delay / tick_duration) % N—— 注意是整数除法,向下取整 - 不能直接用
expire_time % N,系统时间戳(如steady_clock::now().time_since_epoch().count())数值极大,取模会溢出或语义错误 - 若
delay超过一轮(即 >T),需降级到更高层时间轮(多级时间轮场景),单层轮只处理[0, T)内的任务
单线程下如何安全地执行到期任务而不阻塞调度?
执行回调函数前必须从时间轮中移除该定时器,否则可能被重复触发。但移除操作本身不能在遍历槽时直接 erase() —— 会导致迭代器失效或漏掉后续元素。正确做法是:
- 遍历当前槽的
std::vector,把所有待执行的TimerId先拷贝到临时std::vector - 再清空原槽:
slots[current_slot].clear() - 最后逐个查表执行回调:
timers[id].callback() - 绝不允许在回调里调用
addTimer()或cancelTimer()—— 这会破坏当前遍历状态;应通过队列异步投递操作请求
为什么多级时间轮比单层更实用?单层轮的硬伤在哪?
单层轮只能覆盖有限时间范围(比如最大延迟 = 轮周期),且槽越多内存占用越大、缓存行浪费越严重。真实业务中常有几秒到几小时的定时任务共存。多级时间轮(如 3 层:毫秒级、秒级、分钟级)能复用低频槽:
- 第 0 层(256 槽 × 10ms)管 0–2.56s
- 第 1 层(64 槽 × 1s)管 2.56s–66.56s
- 第 2 层(64 槽 × 60s)管 ~1h









