时间轮(timerwheel) 是一种非常经典且高效的定时器管理算法,广泛应用于操作系统内核。就像墙上的钟,表盘上的60个小格子上面挂着这一秒要做的任务,而秒针每一秒走一格。
其主要框架图为:

1、具体实现方式:
/**
* @brief 时间轮类
* 通过循环数组管理定时任务,模拟时钟转动。
*/
class timerwheel{
private:
using weaktask = std::weak_ptr<timertask>;
using ptrtask = std::shared_ptr<timertask>;
int _tick;//当前的秒针,走到哪里释放哪里,释放哪里就相当于执行哪里的任务
int _capacity;//表盘最大数量--其实就是最大延迟时间
// 时间轮槽位:每个槽位是一个数组,存放该秒需要处理(或存活)的任务的 shared_ptr
// 只要 shared_ptr 在这个数组里,引用计数就 > 0,任务就不会析构。
std::vector<std::vector<ptrtask>> _wheel;
// 索引表:通过 id 快速找到任务对象。
// 使用 weak_ptr 是为了不增加引用计数,避免干扰生命周期管理。
std::unordered_map<uint64_t,weaktask> _timers;
private:
// 从索引表中移除定时器记录
void removetimer(uint64_t id){
auto it = _timers.find(id);
if(it != _timers.end()){
_timers.erase(it);
}
}
public:
timerwheel():_capacity(60),_tick(0),_wheel(_capacity){}
/**
* @brief 添加定时任务
* @param id 任务id
* @param delay 延迟多少秒执行
* @param cb 任务回调
*/
void timeradd(uint64_t id,uint32_t delay,const taskfunc &cb){
// 1. 创建新任务对象,引用计数初始化为 1
ptrtask pt(new timertask(id,delay,cb));
// 2. 绑定 release 回调,让 task 析构时能把自己从 _timers 里面删掉
pt->setrelease(std::bind(&timerwheel::removetimer,this,id));
// 3. 计算放置在时间轮的哪个槽位
// 比如当前 tick 是 0,延迟 5 秒,则放在下标 5 的位置
int pos = (_tick+delay)%_capacity;
// 4. 将 shared_ptr 放入对应的槽位(引用计数 +1)
_wheel[pos].push_back(pt);
// 5. 记录到索引表(weak_ptr 不增加引用计数)
_timers[id] = weaktask(pt);
}
/**
* @brief 刷新定时任务(续命)
* 类似于 tcp 的 keepalive,如果连接有活动,就重置它的超时时间。
*/
void timerrefresh(uint64_t id){
auto it = _timers.find(id);
if(it == _timers.end()){
return;//没找到定时任务
}
// 2. 尝试将 weak_ptr 提升为 shared_ptr
// 如果对象还没析构,pt 就不为空
ptrtask pt = it->second.lock();
// 3. 重新计算新的槽位
int delay = pt->delaytime();
int pos = (_tick + delay)%_capacity;
// 4. 将 shared_ptr 再次加入新槽位
// 注意:此时该任务对象可能同时存在于多个槽位中(旧槽位和新槽位)。
// 只要还有一个槽位持有它,引用计数就不为0,它就不会析构。
_wheel[pos].push_back(pt);
}
/**
* @brief 取消定时任务
*/
void timercancel(uint64_t id){
auto it = _timers.find(id);
if(it == _timers.end()){
return;//没找到定时任务
}
ptrtask pt = it->second.lock();
if(pt)pt->cancel();// 仅仅设置标志位,等待自然析构时不执行回调
}
/**
* @brief 驱动时间轮走动一格
* 通常由一个每秒触发一次的定时器(如 timerfd)调用
*/
void runtimertask(){
// 1. 秒针向前走一步
_tick = (_tick + 1)%_capacity;
// 2. 清空当前秒针指向的槽位
// vector::clear() 会析构里面所有的 shared_ptr。
// 如果某个 task 的引用计数因此减为 0,就会调用 ~timertask(),从而执行任务。
// 如果该 task 之前被 refresh 过,它还会存在于后续的槽位中,引用计数 > 0,这里 clear 不会导致它析构。
_wheel[_tick].clear();//清空指定位置的数组
}
};2、定时器任务类
/**
* @brief 定时器任务类
* 封装了一个具体的定时任务,利用 raii 机制,
* 当该对象被销毁时(引用计数归零),触发任务执行。
*/
class timertask{
private:
uint64_t _id;//定时器任务id
uint32_t _timeout;//定时器任务的超时时间(延迟时间)
bool _canceled;//false 表示没有被取消,true-表示被取消
taskfunc _task_cb;//定时器定时任务
releasefunc _release;//删除timerwheel中保存的定时器对象信息
public:
timertask(uint64_t id,uint32_t delay,const taskfunc &cb):
_id(id),_timeout(delay),_task_cb(cb){}
/**
* @brief 析构函数
* 核心逻辑所在:当 shared_ptr 计数减为 0 时,对象析构。
* 此时检查是否被取消,如果没有取消,则执行定时任务。
*/
~timertask() {
if (!_canceled && _task_cb) {
_task_cb(); // 执行任务
}
if (_release) {
_release(); // 从时间轮的索引 map 中删除自己
}
}
// 设置任务取消状态
void cancel(){_canceled = true;}
// 设置清理回调(用于从 map 中移除记录)
void setrelease(const releasefunc &cb){_release = cb;}
// 获取延迟时间
uint32_t delaytime(){return _timeout;}
};到此这篇关于c++模拟实现时间轮模式的文章就介绍到这了,更多相关c++时间轮模式内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论