C++模拟实现时间轮模式(推荐)

 更新时间:2025年11月27日 09:59:23   作者:是小胡嘛  
文章介绍了时间轮(TimerWheel)的原理和实现方式,这是一种高效的定时器管理算法,常用于操作系统内核,时间轮通过类似钟表的机制管理定时任务,每过一秒,秒针移动一格,触发相应任务,感兴趣的朋友跟随小编一起看看吧

时间轮(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++时间轮模式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:

相关文章

  • C++双向链表的增删查改操作方法讲解

    C++双向链表的增删查改操作方法讲解

    相较单链表,双向链表除了data与next域,还多了一个pre域用于表示每个节点的前一个元素。这样做给双向链表带来了很多优势。本文主要介绍了双向链表的实现,需要的可以参考一下
    2023-03-03
  • C++简明图解分析静态成员与单例设计模式

    C++简明图解分析静态成员与单例设计模式

    与静态数据成员不同,静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员,静态成员函数没有this指针。既然它没有指向某一对象,也就无法对一个对象中的非静态成员进行默认访问
    2022-06-06
  • c语言中static修饰函数的方法及代码

    c语言中static修饰函数的方法及代码

    在本篇内容里小编给大家分享的是一篇关于c语言中static如何修饰函数的知识点内容,有需要朋友们可以跟着学习下。
    2021-10-10
  • C++反射的一种实现方法详解

    C++反射的一种实现方法详解

    这篇文章主要给大家介绍了关于C++反射的一种实现方法,文中通过示例代码介绍的非常详细,对大家学习或者使用C++具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-06-06
  • C语言实现学生信息管理系统(文件操作)

    C语言实现学生信息管理系统(文件操作)

    这篇文章主要介绍了C语言实现学生信息管理系统,增加了文件操作,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • C++变量多维分类的具体使用

    C++变量多维分类的具体使用

    C++变量可从作用域/生命周期、存储类型、数据类型三个维度分类,本文就来详细的介绍一下C++变量的多维分类的具体使用,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2025-09-09
  • 探讨++i与i++哪个效率更高

    探讨++i与i++哪个效率更高

    i++总是要创建一个临时对象,在退出函数时还要销毁它,而且返回临时对象的值时还会调用其拷贝构造函数
    2013-10-10
  • 基于select、poll、epoll的区别详解

    基于select、poll、epoll的区别详解

    本篇文章是对select、poll、epoll之间的区别进行了详细的分析介绍。需要的朋友参考下
    2013-05-05
  • C语言中const与指针使用方法总结

    C语言中const与指针使用方法总结

    这篇文章主要介绍了C语言中const与指针使用方法总结的相关资料,需要的朋友可以参考下
    2017-10-10
  • c++中的指针最全总结

    c++中的指针最全总结

    指针是整个C++的精髓所在,只有精通了指针才可以说是掌握了C++,可以说学习C++的过程是个熟练掌握和使用指针的过程,下面这篇文章主要给大家介绍了关于c++中指针的相关资料,需要的朋友可以参考下
    2024-04-04

最新评论