必须用分层时间轮结构将单次插入/删除稳定在o(1),设计三级时间轮(毫秒/秒/分钟轮各64槽,总覆盖约68小时),映射用位运算,超时任务入惰性扫描桶;结合无锁队列与预分配内存池。

在高并发服务中管理数百万个定时任务时,传统std::chrono+std::thread轮询或std::priority_queue逐个唤醒的方式会导致CPU飙升、延迟抖动剧烈、插入删除时间复杂度退化为O(n)——必须用分层时间轮结构将单次插入/删除稳定在O(1),并利用无锁队列和内存池规避动态分配开销。
设计三级时间轮结构与槽位映射逻辑
一级轮(毫秒轮)64槽,覆盖0–63ms;二级轮(秒轮)64槽,每槽代表1s,覆盖0–63s;三级轮(分钟轮)64槽,每槽代表1min,覆盖0–63min。总跨度64×64×60=245760秒(约68小时),满足绝大多数业务场景。
时间戳t(毫秒)映射规则:毫秒槽 = t & 63;秒槽 = (t >> 6) & 63;分钟槽 = (t >> 12) & 63。注意必须用位运算而非除法,【否则在高频插入路径中会引入不可接受的除法指令延迟】。
当t超出三级轮范围时,直接放入超时桶(overflow bucket),由独立线程惰性扫描——不阻塞主调度循环。
实现无锁任务队列与内存池绑定
方法一:使用boost::lockfree::queue作为待执行任务缓冲区,构造时指定固定容量(如131072),避免运行时扩容。
方法二:手写SPSC无锁队列(Single-Producer Single-Consumer),生产者为时间轮tick线程,消费者为工作线程池。关键点是用std::atomic
方法三:任务对象全部从预分配内存池获取。定义struct TimerTask { uint64_t expire_ms; void(*cb)(); TimerTask* next; },初始化时malloc(1
编写核心tick驱动与任务触发逻辑
第一步:启动一个专用std::thread,设置sched_setscheduler(SCHED_FIFO, 1)提升实时优先级。
第二步:循环执行usleep(1000)→获取当前毫秒时间now_ms→计算毫秒轮偏移slot = now_ms & 63→遍历该槽所有TimerTask节点。
第三步:对每个节点,检查expire_ms ≤ now_ms。若成立,则将其cb函数指针写入SPSC队列尾部;否则根据剩余时间重新哈希到更高级时间轮对应槽位——这里必须先从原槽链表unlink,再insert到新槽头,【漏掉unlink会导致同一任务被重复触发】。
第四步:每64次tick(即1s)同步推进秒轮指针,执行相同流程;每64次秒轮推进(即64s)推进分钟轮指针。推进操作本身是原子整数自增,无锁。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











