用最小堆实现通用的高效定时器组件

用最小堆实现通用的高效定时器组件

文章目录

  • 用最小堆实现通用的高效定时器组件
    • 开篇
    • 解决方案
    • 类图
    • 源码实现
    • 测试
    • 总结

开篇

  在程序开发过程中,定时器会经常被使用到。而在Linux应用开发中,系统定时器资源有限,进程可创建的定时器数量会受到系统限制。假如随便滥用定时器,会导致定时器资源不足,其他模块便无法申请到定时器资源。
  如上,假如同一进程中多个模块,需要同时申请不同周期定时器,就会导致模块创建定时器失败。

解决方案

  为解决定时器资源紧缺的问题,通常有以下几种方案:

  • 最小堆方式
    ① 首先创建一个系统定时器,设置为一次性触发。
    ② 其次基于二叉堆数据结构,将每个定时任务按照时触发时间戳先后顺序依次排列。
    ③ 每次取堆顶定时器任务时间戳,计算出触发时间,启动并更新系统定时器触发时间。
    ④ 定时器触发后,检查堆顶部的定时任务是否超时,超时触发对应事件,将定时器任务移除堆顶,重复③。(若定时任务为周期任务,则将其按照下次触发时间戳插入至二叉堆)

  • 时间轮方式
    ① 首先创建一个系统定时器,设置为周期性触发,周期为多个定时任务可共用的最小颗粒度。
    ② 定义环形数组,将时间划分为多个槽,每个槽放多个定时任务。
    ③ 定时器按照周期触发,触发后遍历每个槽的定时任务,并触发对应事件。

两者相比,各有优劣。最小堆方式精度更高,时间轮方式则胜在效率。在定时任务数量不庞大的情况下,最小堆方式更合适。本篇主要介绍最小堆的实现。

类图

  通过对定时器功能的理解,可以将其抽象为三个类:系统定时器,定时器任务,定时器任务管理。其类图如下:

定时器管理组件

  • 系统定时器(SystemTimer)
    负责封装Linux 定时器接口,向外提供系统定时器的使用接口。主要包含如下功能:
    ① 创建定时器
    ② 启动定时器
    ③ 停止定时器
    ④ 销毁定时器资源

  • 定时器任务(Timer)
    负责缓存定时任务属性的数据结构。主要包含如下数据:
    ① 触发时间间隔
    ② 下次触发时间戳
    ② 触发次数
    ③ 已触发次数计数
    ④ 定时器触发响应事件
    ⑤ 预定定时器的模块ID

  • 定时器任务管理(TimerManager)
    负责持有系统定时器和定时任务的管理。主要包含如下功能:
    ① 初始化、启动、结束、销毁系统定时器
    ② 接收和缓存定时任务预约事件
    ③ 维护定时任务容器,按照定时任务容器时间序更新系统定时器触发时间

源码实现

编程环境

  1. 编译环境: Linux环境
  2. 语言: C++语言

接口定义

  • 系统定时器(SystemTimer)
class SprSystemTimer : public SprObserver
{
public:
    SprSystemTimer(ModuleIDType id, const std::string& name, std::shared_ptr<SprMediatorProxy> mediatorPtr);
    ~SprSystemTimer();
    SprSystemTimer(const SprSystemTimer&) = delete;
    SprSystemTimer& operator=(const SprSystemTimer&) = delete;
    SprSystemTimer(SprSystemTimer&&) = delete;
    SprSystemTimer& operator=(SprSystemTimer&&) = delete;

    int ProcessMsg(const SprMsg& msg);

    int Init();
    int InitTimer();
    int StartTimer(uint32_t intervalInMilliSec);
    int StopTimer();
    int DestoryTimer();

private:
    bool mTimerRunning;
    int  mTimerFd;
};
  • 定时器任务(Timer)
class SprTimer
{
public:
    SprTimer(uint32_t moduleId, uint32_t msgId, uint32_t repeatTimes, uint32_t delayInMilliSec, uint32_t intervalInMilliSec);
    SprTimer(const SprTimer& timer);
    ~SprTimer();

    bool operator < (const SprTimer& t) const;
    bool IsExpired() const;
    uint32_t GetTick() const;
    uint32_t GetModuleId() const { return mModuleId; }
    uint32_t GetMsgId() const { return mMsgId; }
    uint32_t GetIntervalInMilliSec() const { return mIntervalInMilliSec; }
    uint32_t GetExpired() const { return mExpired; }
    uint32_t GetRepeatTimes() const { return mRepeatTimes; }
    uint32_t GetRepeatCount() const { return mRepeatCount; }

    void SetExpired(uint32_t expired) { mExpired = expired; }
    void RepeatCount() const { mRepeatCount++; }

private:
    uint32_t mModuleId;
    uint32_t mMsgId;
    uint32_t mIntervalInMilliSec;
    uint32_t mExpired;
    uint32_t mRepeatTimes;
    mutable uint32_t mRepeatCount;
};
  • 定时器任务管理(TimerManager)
class SprTimerManager : public SprObserver
{
public:
    virtual ~SprTimerManager();
    int Init();
    static SprTimerManager* GetInstance(ModuleIDType id, const std::string& name, std::shared_ptr<SprMediatorProxy> mediatorPtr, std::shared_ptr<SprSystemTimer> systemTimerPtr);
private:
    SprTimerManager(ModuleIDType id, const std::string& name, std::shared_ptr<SprMediatorProxy> mediatorPtr, std::shared_ptr<SprSystemTimer> systemTimerPtr);

    int DeInit();
    int InitSystemTimer();
    int ProcessMsg(const SprMsg& msg) override;
    int PrintRealTime();

    // --------------------------------------------------------------------------------------------
    // - Module's timer book manager functions
    // --------------------------------------------------------------------------------------------
    int AddTimer(uint32_t moduleId, uint32_t msgId, uint32_t repeatTimes, int32_t delayInMilliSec, int32_t intervalInMilliSec);
    int AddTimer(const SprTimer& timer);
    int DelTimer(const SprTimer& timer);
    int UpdateTimer();
    int CheckTimer();
    uint32_t NextExpireTimes();

    // --------------------------------------------------------------------------------------------
    // - Message handle functions
    // --------------------------------------------------------------------------------------------
    void MsgRespondStartSystemTimer(const SprMsg &msg);
    void MsgRespondStopSystemTimer(const SprMsg &msg);
    void MsgRespondAddTimer(const SprMsg &msg);
    void MsgRespondDelTimer(const SprMsg &msg);
    void MsgRespondSystemTimerNotify(const SprMsg &msg);
    void MsgRespondClearTimersForExitComponent(const SprMsg &msg);

private:
    bool mEnable;                                       // Component init status
    std::set<SprTimer> mTimers;                         // sort by SprTimer.mExpired from smallest to largest
    std::shared_ptr<SprSystemTimer> mSystemTimerPtr;    // SysTimer object
};

TimerManager
中存储定时任务的容器用的std::set<Timer>,可以自定义按照时间戳从小到大排序,就不用自己实现二叉堆结构了。

如下是TimerManager中定时器触发的业务逻辑代码:
① 定时器触发后,从头遍历任务容器。
② 若当前任务已超时且任务未失效,通知定时器触发事件。将当前任务缓存至失效容器,若为重复定时器,更新时间戳,再次插入任务容器。
③ 若当前任务未到期(说明后续任务都未到期),退出容器遍历。与②互斥。
④ 从任务容器中,删除②中缓存的失效容器
⑤ 当前任务容器若为空,停止系统定时器。

void SprTimerManager::MsgRespondSystemTimerNotify(const SprMsg &msg)
{
    set<SprTimer> deleteTimers;

    // loop: Execute the triggered timers, timers are sorted by Expired value from smallest to largest
    for (auto it = mTimers.begin(); it != mTimers.end(); ++it) {
        if (it->IsExpired()) {
            if (it->GetRepeatTimes() == 0 || (it->GetRepeatCount() + 1) < it->GetRepeatTimes()) {
                SprTimer t(*it);

                // loop: update timer valid expired time
                uint32_t tmpExpired = t.GetExpired();
                do {
                    tmpExpired += t.GetIntervalInMilliSec();
                    t.RepeatCount();
                } while (tmpExpired < it->GetTick());

                if (it->GetRepeatTimes() == 0 || (it->GetRepeatCount() + 1) < it->GetRepeatTimes()) {
                    t.SetExpired(tmpExpired);
                    AddTimer(t);
                }
            }

            // Notify expired timer event to the book component
            SprMsg msg(it->GetModuleId(), it->GetMsgId());
            NotifyObserver(msg);
            it->RepeatCount();

            deleteTimers.insert(*it);
        } else {
            break;
        }
    }

    // Delete expired timers
    for (const auto& timer : deleteTimers) {
        DelTimer(timer);
    }

    // Set next system timer
    uint32_t msgId = mTimers.empty() ? SIG_ID_TIMER_STOP_SYSTEM_TIMER : SIG_ID_TIMER_START_SYSTEM_TIMER;
    SprMsg sysMsg(msgId);
    SendMsg(sysMsg);
    // SPR_LOGD("Current total timers size = %d\n", (int)mTimers.size());
}

测试

测试一个2s的定时器:

56 DebugCore D: msg id: SIG_ID_DEBUG_TIMER_TEST_2S 2024-03-03 19:26:16.586
56 DebugCore D: msg id: SIG_ID_DEBUG_TIMER_TEST_2S 2024-03-03 19:26:18.586
56 DebugCore D: msg id: SIG_ID_DEBUG_TIMER_TEST_2S 2024-03-03 19:26:20.586
56 DebugCore D: msg id: SIG_ID_DEBUG_TIMER_TEST_2S 2024-03-03 19:26:22.585

总结

  • 对于定时器容器,本篇用到了STL接口的std::set<Timer>容器,通过重载Timer运算符<,实现按照时间戳(mExpired)从小到大排序。
  • 将定时器任务抽象处三个类,各自负责自己的业务,逻辑上更加清晰明了。
  • 使用一个系统定时器资源,完成所有定时任务的响应。实现基础功能的同时,降低对系统定时资源的消耗。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/484731.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Priority Queue实现栈和队列

在排序算法中&#xff0c;堆排序利用了完全二叉树形式的堆结构&#xff0c;结合了插入排序与合并排序的优点&#xff0c;能够以 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)的时间完成排序。优先级队列是可基于堆结构进行实现的一种数据结构&#xff0c;在计算机系统中可以用来记录…

【现代C++】可变参数模板

现代C中的可变参数模板是C11引入的一个功能&#xff0c;允许模板接受可变数量的参数&#xff0c;使得模板编程更加灵活和强大。 1. 基本用法 可变参数模板允许您创建接受任意数量参数的函数或类模板。 template<typename... Args> void func(Args... args) {// 处理参…

C++ 基本运算

何谓运算符和操作数 基本运算 1、双目运算 2、单目运算 3、赋值表达式 表达形式&#xff1a; <变量><表达式>; 表达式是指各种运算符把常量、变量&#xff0c;函数等运算对象连接起来的具有实际意义并符合C语法规则的式子。赋值是指表达式的值赋给一个变量。 …

基于SSM的NEUQ宿舍管理系统的设计与实现

基于SSM的NEUQ宿舍管理系统的设计与实现 获取源码——》公主号&#xff1a;计算机专业毕设大全 获取源码——》公主号&#xff1a;计算机专业毕设大全

LabVIEW电动汽车直流充电桩监控系统

LabVIEW电动汽车直流充电桩监控系统 随着电动汽车的普及&#xff0c;充电桩的安全运行成为重要议题。通过集成传感器监测、单片机技术与LabVIEW开发平台&#xff0c;设计了一套电动汽车直流充电桩监控系统&#xff0c;能实时监测充电桩的温度、电压和电流&#xff0c;并进行数…

PMP适合哪些人?考试PMP有什么职业要求吗?

威班PMP 3A路过拿证学员 。PMP认证没听说过有啥职业的要求&#xff0c;也没听说过限制在哪些行业可用&#xff0c;根据我考后经验所了解&#xff0c;它并不只作用在某一个领域&#xff0c;知识点也是项目管理相关的工作都能用到&#xff0c;毕竟这些都是通用的专业实践。如果非…

linux命令学习——sort

sort可以对文本文件进行“排序”&#xff0c;比如-n可以对文本&#xff0c;按照首行字母数字顺序排序 -r参数可以对排序结果进行反转 -u参数可以对查看结果去重

3.18_C++_day6_作业

作业要求&#xff1a; 程序代码&#xff1a; #include <iostream>using namespace std;class Animal { private:string name;string color;int *age; public://无参构造函数Animal(){cout << "Animal::无参构造函数" << endl;}//有参构造函数Anim…

基于深度学习的生活垃圾智能分类系统(微信小程序+YOLOv5+训练数据集+开题报告+中期检查+论文)

摘要 本文基于Python技术&#xff0c;搭建了YOLOv5s深度学习模型&#xff0c;并基于该模型研发了微信小程序的垃圾分类应用系统。本项目的主要工作如下&#xff1a; &#xff08;1&#xff09;调研了移动端垃圾分类应用软件动态&#xff0c;并分析其优劣势&#xff1b;…

附近最小 单调队列 滑动窗口 蓝桥杯

q[t]i 的执行过程如下&#xff1a; 首先&#xff0c;t 的值会先自增 1。然后&#xff0c;新值 i 被赋给 q[t]&#xff0c;即元素 i 被插入到数组 q 的下标为 t 的位置上。 q[t]i 的执行过程如下&#xff1a; 首先&#xff0c;i 的值被赋给 q[t]&#xff0c;即元素 i 被插入到数…

深度学习pytorch——可视化visdom(持续更新)

安装可看&#xff1a;e: Error while finding module specification for ‘visdom.server‘ (ModuleNotFoundError: No module name-CSDN博客 在命令行窗口使用python -m visdom.server&#xff0c;会出现一个web地址&#xff0c;在浏览器中访问&#xff0c;即可看见在python中…

2024年noc指导教师认证测评参考试题题目3-4合集

[noc指导教师认证] 测评参考试题 说明:NOC教师指导认证考试题目是从题库里抽题,因此每位老师每次考试题目都不一样以下题目为测试考试时收集到的一些题目,作为辅助提供给各位老师,老师们可以记住题目及答案的具体内容 (选项顺序会变),以免考试时遇到。2024年的做的题目有的…

C++基础9:继承与派生

此专栏为移动机器人知识体系下的编程语言中的 C {\rm C} C从入门到深入的专栏&#xff0c;参考书籍&#xff1a;《深入浅出 C {\rm C} C》(马晓锐)和《从 C {\rm C} C到 C {\rm C} C精通面向对象编程》(曾凡锋等)。 8.继承与派生 8.1 继承与派生基础 继承与派生举例理解&#…

C++函数参数传递

目录 传值参数 指针形参 传引用参数 使用引用避免拷贝 使用引用形参返回额外信息 const形参和实参 指针或引用形参与const 数组形参 管理指针形参 使用标记指定数组长度 使用标准库规范 显式传递一个表示数组大小的形参 数组形参和const 数组引用形参 传递多维数…

mysql基础1sql分类

mysql基础 [rootvm ~]# docker run -itd -p 3306:3306 -e "MYSQL_ROOT_PASSWORD123456" mysql:5.7.26通用语法 1). SQL语句可以单行或多行书写&#xff0c;以分号结尾。 2). SQL语句可以使用空格/缩进来增强语句的可读性。 3). MySQL数据库的SQL语句不区分大小写…

(ES6)前端八股文修炼Day2

1. let const var 的区别 var&#xff1a; var 是在 ES5 中引入的声明变量的关键字。 具有函数作用域&#xff0c;而不是块作用域&#xff0c;这意味着使用 var 声明的变量在函数内部是可见的。 变量可以被重复声明&#xff0c;而且变量的值可以在声明前使用&#xff0c;这可能…

阿里云服务器租用价格表,2024年1个月和一年优惠价格表

2024年腾讯云服务器优惠价格表&#xff0c;一张表整理阿里云服务器最新报价&#xff0c;阿里云服务器网整理云服务器ECS和轻量应用服务器详细CPU内存、公网带宽和系统盘详细配置报价单&#xff0c;大家也可以直接移步到阿里云CLUB中心查看 aliyun.club 当前最新的云服务器优惠券…

每日一题 --- 反转链表[力扣][Go]

反转链表 题目&#xff1a;206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&a…

AtCoder Regular Contest 174 A~E

A.A Multiply&#xff08;贪心&#xff09; 题意&#xff1a; 给你一个长度为 N N N 、 A ( A 1 , A 2 , … , A N ) A(A_1,A_2,\dots,A_N) A(A1​,A2​,…,AN​) 和整数 C C C 的整数序列。 在进行最多一次以下操作后&#xff0c;求 A A A 中元素的最大可能和&#xff…

为什么安装了4GB的内存条,却显示只有3.8GB?

朋友们&#xff0c;对于计算机而言&#xff0c;其基本包含三部分。 第一&#xff0c;CPU; 第二&#xff0c;存储器&#xff08;内存、物理内存&#xff09;&#xff1b;第三&#xff0c;输入设备、输出设备。 32位的地址总线&#xff0c;其地址范围就是 CPU 访问内存&#xf…