zoukankan      html  css  js  c++  java
  • Linux下一种高效多定时器实现

    Linux下一种高效多定时器实现

    作者:LouisozZ

    日期:2018.08.29

    运行环境说明

    由于在 Linux 系统下一个进程只能设置一个时钟定时器,所以当应用需要有多个定时器来共同管理程序运行时,就需要自行实现多定时器管理。

    本文就是基于这种需求,在实际编码工作的基础上总结而出,希望跟大家共享,也欢迎大家讨论指正。

    多定时器原理

    在一个进程只能有一个定时器的前提条件下,想要实现多定时器,就得用到那个进程能利用的唯一的定时器,这个定时器是由操作系统提供的,通过系统提供的接口来设置,常用的有 alarm() 和 setitimer(),不论用什么,后文统一称作系统定时接口,这两个接口的区别在很多博客里都有,不怎么清楚的可以自行搜索,这里就不再赘述(我比较懒,打字多对肾不好)。通过它们产生的定时信号作为基准时间,来管理实现多定时器。

    举个栗子,糖炒板栗。利用系统定时接口设置了基准定时器,基准定时器每秒产生一个 SIGALRM 信号(系统时钟的超时时间到了之后会向进程发送信号以通知定时超时,alarm() 和 setitimer() 都是向进程发送 SIGALRM 信号,关于 Linux ‘信号’ 的内容,可以参考 《UNIX环境高级编程》),产生两个 SIGALRM 信号的时间间隔,就是多定时器的基准时间。当然,上述的基准时间是一秒,如果你是每隔 50ms 产生一个 SIGALRM 信号,那么多定时器的基准时间就是 50ms 。当有了基准时间之后,就可以对它进行管理,可以设置多个定时任务,现有两个定时任务,Timer1_Task , Timer2_Task, 其中 Timer1_Task 的定时时长为 10 个基准时间,Timer2_Task 为 15 个基准时间,则每产生 10 个 SIGALRM 信号,就表示 Timer1_Task 定时器超时到达,执行一次 Timer1_Task 的超时任务,每产生 15 个 SIGALRM 信号,则执行一次 Timer2_Task 超时任务,当产生的 SIGALRM 信号个数是 30 (10 和 15 的最小公倍数),则 Timer1_Task 和 Timer2_Task 的超时任务都要被执行。

    好了,原理讲完了,下面就是本文的重点了。

    ————————————— 说重点专用阉割线 —————————————

    ————————————— 说重点专用分割线 —————————————

    高效多定时器

    数据结构

    由一个全局链表 g_pTimeoutCheckListHead 来管理超时任务。链表的每个节点是一个 tMultiTimer 结构体:

        typedef void TimeoutCallBack(void*);    //回调函数格式
        typedef struct tMultiTimer
        {
            uint8_t nTimerID;       //设置宏定义
            uint32_t nInterval;     //定时时长
            uint32_t nTimeStamp;    //时间戳
            bool bIsSingleUse;      //是否单次使用
            bool bIsOverflow;       //用于解决计数溢出问题
    
            TimeoutCallBack *pTimeoutCallbackfunction;  //回调函数
            void* pTimeoutCallbackParameter;            //回调函数参数
    
            struct tMultiTimer* pNextTimer;             //双向链表后驱指针
            struct tMultiTimer* pPreTimer;              //双向链表前驱指针
            struct tMultiTimer* pNextHandle;            //二维链表相同超时Timer节点
        }tMultiTimer;
    
        tMultiTimer* g_pTimeoutCheckListHead;           //管理多定时器的全局链表
        bool g_bIs_g_nAbsoluteTimeOverFlow;             //基准时间计次器溢出标志位
        uint32_t g_nAbsoluteTime;                       //基准时间计次器
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    (各个成员变量的意义在后文会逐一介绍,客官莫急)

    这个是一个二维双向链表,第一维根据时间戳,即绝对时间,按照先后顺序连接每一个 tMultiTimer 节点,当有多个超时任务的超时时刻是相同的时候,只有一个节点位于第一维,其余接在上一个相同超时时刻 tMultiTimer 节点的 pNextHandle 上,图示如下:
    这里写图片描述

    多定时管理流程

    超时检测与运行

    首先需要调用系统定时接口,设置进程的定时器,产生 SIGALRM 信号,每一次 SIGALRM 到来时,全局的基准时间计次器 g_nAbsoluteTime 自加,由于 g_nAbsoluteTime 是无符号类型,当其溢出时,是回到 0 ,每次溢出就把 g_bIs_g_nAbsoluteTimeOverFlow 取反。

    每个  tMultiTimer 节点都有 :
    
        uint32_t nInterval;             //定时时长
        uint32_t nTimeStamp;        //时间戳
    
    • 1
    • 2
    • 3
    • 4

    其中 nTimeStamp 这个值,是由 nInterval + g_nAbsoluteTime 计算而来,在把这个节点加入到全局链表的时刻计算的 ,这个和作为超时的绝对时间保存在结构体中,当计算的和溢出时,bIsOverflow 取反。通过这两个溢出标志位,可以用来解决溢出之后判断是否超时的问题,具体如下:

    每一次基准时间超时,就检查链表的第一个节点的超时时间 nTimeStamp 是否小于全局绝对时间 g_nAbsoluteTime ,如果 g_bIs_g_nAbsoluteTimeOverFlow 与 bIsOverflow 不相等,则链表第一个节点的超时时间一定未到达,因为 bIsOverflow 的取反操作一定是先于 g_bIs_g_nAbsoluteTimeOverFlow ,如果一样则比较数值大小(初始化的时候两个溢出标志位是一样的)。当全局绝对时间大于等于第一个节点的时间戳,则把该节点及其 pNextHandle 指向的第二维链表取下,并更新 g_pTimeoutCheckListHead,然后依次执行所取下链表的回调函数。执行完之后(或者之前,根据实际情况定),判断 bIsSingleUse 成员变量,如果为 true 则表示是单次的计数器,仅执行一次,执行完回调之后则定时任务完成。如果是 false ,怎表示是定时任务,则重新执行一次添加超时任务。(添加超时任务看下一节)

    添加超时任务

    添加超时任务(添加一个 tMultiTimer 节点到全局链表 g_pTimeoutCheckListHead 中)的时候,指定超时时长,即间隔多少个基准时间,赋值给这个任务的成员变量 nInterval ,然后计算

        nTimeStamp = nInterval  + g_nAbsoluteTime; 
        if(nTimeStamp  < g_nAbsoluteTime) 
            bIsOverflow = ~bIsOverflow;
    • 1
    • 2
    • 3

    接着搜索 g_pTimeoutCheckListHead ,如果有相同时间戳,则添加到其 pNextHandle 指向位置,如果没有相同时间戳节点,找到比要插入的节点时间戳大的节点,然后把当前节点插入到其前方。

    对于链表中已经有相同 ID 的 tMultiTimer 节点的情况,再次添加则表示更新该定时任务,取消之前的定时任务重新插入到链表中。

    取消超时任务

    直接把对应 ID 的 tMultiTimer 节点从 g_pTimeoutCheckListHead 链表中摘掉即可。

    效率分析

    g_pTimeoutCheckListHead 链表中的 tMultiTimer 节点数量,是总共设置的超时任务数量,假设为 n,添加一个超时任务(节点)的最坏情况是遍历 n 个节点。

    检查是否有任务超时所用的时间是常数时间,只检查第一个节点。

    对于定时任务的再次插入问题,如果定时任务间隔时间越短,其反复被插入的次数越多,但是由于定时时间短,所以在链表中的插入位置也就越靠前,将快速找到插入点;如果定时任务间隔时间越长,越可能遍历整个链表在末尾插入,但是由于间隔时间长,重复插入的频率则很低。

    与一种简单的定时器实现相比较:

        if(g_nAbsoluteTime  %  4)
        {
            Timer_Task_1();
        }
        if(g_nAbsoluteTime  %  17)
        {
            Timer_Task_2();
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这种简单实现来说,

    1:每次添加、取消一个定时任务都需要修改定时器源码,复用性不高。

    2:每次检查是否有任务超时需要遍历 n 个定时任务。

    关于多线程下的一些坑

    我在实际项目中使用的环境是多线程的,有四个,我把多定时器管理放在了单独的一个线程里。由于系统定时器接口产生的信号是发送给进程的,所以所有的线程都共享这个闹钟信号。一开始我是这么想的,定时器的默认动作是杀死进程,那么给每个线程添加信号捕捉函数,这样的话闹钟信号到了之后不管是那个线程接管了,都能到我指定的处理函数去,可是实际情况并非如此,进程仍然会被杀死。

    后面我用了线程信号屏蔽,把非定时器线程都设置了信号屏蔽字,即闹钟信号不被别的线程可见,这样才能正常运行,至于第一种方法为何不行,现在我还没有找到原因,还是对 Linux 的信号机制不熟,以后看有时间的话把这里搞懂吧。

    main.c

        //配置信号集
        sigset_t g_sigset_mask;
        sigemptyset(&g_sigset_mask);
        sigaddset(&g_sigset_mask,SIGALRM);
    • 1
    • 2
    • 3
    • 4

    other_thread.c

        sigset_t old_sig_mask;
        if(err = pthread_sigmask(SIG_SETMASK,&g_sigset_mask,&old_sig_mask) != 0)
        {
            // pthread_sigmask 设置信号屏蔽
            return ;
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    mulitimer_thread

    void* MultiTimer_thread(void *parameter)
    {
        int err,signo;
        struct itimerval new_time_value,old_time_value;
    
        new_time_value.it_interval.tv_sec = 0;
        new_time_value.it_interval.tv_usec = 1000;
        new_time_value.it_value.tv_sec = 0;
        new_time_value.it_value.tv_usec = 1;
        setitimer(ITIMER_REAL, &new_time_value,NULL);
    
        for(;;)
        {
            err = sigwait(&g_sigset_mask,&signo);//信号捕捉
            if(err != 0)
            {
                return ;
            }
    
            if(signo == SIGALRM)
            {
                SYSTimeoutHandler(signo);
            }
        }
        return ((void*)0);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    multiTimer.c

    #include "multiTimer.h"
    
    /**
     * @function    把一个定时任务添加到定时检测链表中
     * @parameter   一个定时器对象,可以由全局变量 g_aSPPMultiTimer 通过 TIMER_ID 映射得到
    */
    static void AddTimerToCheckList(tMultiTimer* pTimer)
    {
        tMultiTimer* pEarliestTimer = NULL;
        tMultiTimer* pEarliestTimer_pre = NULL;
    
        CDebugAssert(pTimer->nInterval != 0);
    
        pTimer->nTimeStamp = g_nAbsoluteTime + pTimer->nInterval;
        if(pTimer->nTimeStamp < g_nAbsoluteTime)
            pTimer->bIsOverflow = !(pTimer->bIsOverflow);
        if(g_pTimeoutCheckListHead == NULL)
        {
            g_pTimeoutCheckListHead = pTimer;
            g_pTimeoutCheckListHead->pNextTimer = NULL;
            g_pTimeoutCheckListHead->pPreTimer = NULL;
            g_pTimeoutCheckListHead->pNextHandle = NULL;
            return;
        }
        else
        {
            pEarliestTimer = g_pTimeoutCheckListHead;
            while(pEarliestTimer != NULL)
            {
                //如果超时时间小于新加的timer则直接跳过;
                if((pEarliestTimer->bIsOverflow != pTimer->bIsOverflow) || (pEarliestTimer->nTimeStamp < pTimer->nTimeStamp))
                {
                    pEarliestTimer_pre = pEarliestTimer;
                    pEarliestTimer = pEarliestTimer->pNextTimer;
                }    
                else
                {
                    if(pEarliestTimer->nTimeStamp == pTimer->nTimeStamp)    //超时时刻相等,直接添加到相同时刻处理列表的列表头
                    {
                        pTimer->pNextHandle = pEarliestTimer->pNextHandle;
                        pEarliestTimer->pNextHandle = pTimer;
                        return;
                    }
                    else                                                    //找到了超时时刻大于新加入timer的第一个节点
                    {
                        if(pEarliestTimer->pPreTimer == NULL)               //新加入的是最早到达超时时刻的,添加到链表头
                        {
                            pEarliestTimer->pPreTimer = pTimer;
                            pTimer->pNextTimer = pEarliestTimer;
                            pTimer->pPreTimer = NULL;
                            pTimer->pNextHandle = NULL;
                            g_pTimeoutCheckListHead = pTimer;
                            return;
                        }
                        else                                                //中间节点
                        {
                            pEarliestTimer->pPreTimer->pNextTimer = pTimer;
                            pTimer->pNextTimer = pEarliestTimer;
                            pTimer->pPreTimer = pEarliestTimer->pPreTimer;
                            pEarliestTimer->pPreTimer = pTimer;
                            pTimer->pNextHandle = NULL;
                            return;
                        }
                    }
                }  
            }
            if(pEarliestTimer == NULL)                                      //新加入的timer超时时间是最晚的那个
            {
                pEarliestTimer_pre->pNextTimer = pTimer;
                pTimer->pPreTimer = pEarliestTimer_pre;
                pTimer->pNextTimer = NULL;
                pTimer->pNextHandle = NULL;
            }
            return;
        }
    }
    
    /**
     * @function    设置一个定时任务,指定超时间隔与回调函数,当超时到来,自动执行回调
     * @parameter1  TIMER_ID    
     * @parameter2  超时间隔时间
     * @parameter3  是否是一次性定时任务
     * @parameter4  回调函数,注意,回调函数的函数形式  void function(void*);
     * @parameter5  void* 回调函数的参数,建议用结构体强转成 void*,在回调函数中再强转回来  
     * @return      错误码 
    */
    uint8_t SetTimer(uint8_t nTimerID,uint32_t nInterval,bool bIsSingleUse,TimeoutCallBack* pCallBackFunction,void* pCallBackParameter)
    {
        printf("
    set timer %d
    ",nTimerID);
        tMultiTimer* pChoosedTimer = NULL;
        pChoosedTimer = g_aSPPMultiTimer[nTimerID];
        pChoosedTimer->nInterval = nInterval;
        pChoosedTimer->bIsSingleUse = bIsSingleUse;
        pChoosedTimer->pTimeoutCallbackfunction = pCallBackFunction;
        pChoosedTimer->pTimeoutCallbackParameter = pCallBackParameter;
    
        //如果超时任务链表中已经有这个任务了,先取消,然后再设置,即重置超时任务
        if(pChoosedTimer->pNextTimer != NULL || pChoosedTimer->pPreTimer != NULL)
            CancelTimerTask(nTimerID,CANCEL_MODE_IMMEDIATELY);
    
        AddTimerToCheckList(pChoosedTimer);
        return 0;
    }
    
    /**
     * @function    取消超时检测链表中的指定超时任务
     * @parameter1  要取消的超时任务的ID
     * @parameter2  模式选择,是立即取消,还是下次执行后取消
     * @return      错误码
    */
    uint8_t CancelTimerTask(uint8_t nTimerID,uint8_t nCancelMode)
    {
        printf("
    cancle timer %d
    ",nTimerID);
        tMultiTimer* pEarliestTimer = NULL;
        tMultiTimer* pHandleTimer = NULL;
        tMultiTimer* pHandleTimer_pre = NULL;
        tMultiTimer* pChoosedTimer = NULL;
    
        pEarliestTimer = g_pTimeoutCheckListHead;
        pChoosedTimer = g_aSPPMultiTimer[nTimerID];
    
        if(nCancelMode == CANCEL_MODE_IMMEDIATELY)
        {
            while(pEarliestTimer != NULL)
            {
                pHandleTimer = pEarliestTimer;
                pHandleTimer_pre = NULL;
                while(pHandleTimer != NULL)
                {
                    if(pHandleTimer->nTimerID == nTimerID)
                    {
                        if(pHandleTimer_pre == NULL)
                        {
                            if(pHandleTimer->pNextHandle != NULL)
                            {
                                pEarliestTimer = pHandleTimer->pNextHandle;
                                pEarliestTimer->pPreTimer = pHandleTimer->pPreTimer;
                                if(pHandleTimer->pPreTimer != NULL)
                                    pHandleTimer->pPreTimer->pNextTimer = pEarliestTimer;
                                pEarliestTimer->pNextTimer = pHandleTimer->pNextTimer;
                                if(pHandleTimer->pNextTimer != NULL)
                                    pHandleTimer->pNextTimer->pPreTimer = pEarliestTimer;
                                pHandleTimer->pNextTimer = NULL;
                                pHandleTimer->pPreTimer = NULL;
                                pHandleTimer->pNextHandle = NULL;
                            }
                            else
                            {
                                if(pEarliestTimer->pPreTimer == NULL)
                                {
                                    g_pTimeoutCheckListHead = pEarliestTimer->pNextTimer;
                                    g_pTimeoutCheckListHead->pPreTimer = NULL;
                                    pEarliestTimer->pNextTimer = NULL;
                                }
                                else if(pEarliestTimer->pNextTimer == NULL)
                                {
                                    pEarliestTimer->pPreTimer->pNextTimer = NULL;
                                    pEarliestTimer->pPreTimer = NULL;
                                }
                                else
                                {
                                    pEarliestTimer->pPreTimer->pNextTimer = pEarliestTimer->pNextTimer;
                                    pEarliestTimer->pNextTimer->pPreTimer = pEarliestTimer->pPreTimer;
                                    pEarliestTimer->pPreTimer = NULL;
                                    pEarliestTimer->pNextTimer = NULL;
                                }
                            }
                        }
                        else
                        {
                            pHandleTimer_pre->pNextHandle = pHandleTimer->pNextHandle;
                            pHandleTimer->pNextHandle = NULL;
                        }
                        return 0;
                    }
                    else
                    {
                        pHandleTimer_pre = pHandleTimer;
                        pHandleTimer = pHandleTimer_pre->pNextHandle;
                    }
                }
                pEarliestTimer = pEarliestTimer->pNextTimer;
            }
            #ifdef DEBUG_PRINTF
            printf("
    There is no this timer task!
    ");
            #endif
            return 2;   //出错,超时检测链表中没有这个超时任务
        }
        else if(nCancelMode == CANCEL_MODE_AFTER_NEXT_TIMEOUT)
        {
            pChoosedTimer->bIsSingleUse = true;
            return 0;
        }
        else
        {
            return 1;   //出错,模式错误,不认识该模式
        }
    }
    /**
     * @function    定时器处理函数,用于检测是否有定时任务超时,如果有则调用该定时任务的回调函数,并更新超时检测链表
     *              更新动作:如果超时的那个定时任务不是一次性的,则将新的节点加入到检测超时链表中,否则直接删掉该节点;
     * @parameter   
     * @return
    */
    void SYSTimeoutHandler(int signo)
    {
        //printf("
    enter SYSTimeoutHandler
    ");
        if(signo != SIGALRM)
            return;
        tMultiTimer* pEarliestTimer = NULL;
        tMultiTimer* pWaitingToHandle = NULL;
        tMultiTimer* pEarliestTimerPreHandle = NULL;
    
        if(g_pTimeoutCheckListHead != NULL)
        {
            if((g_pTimeoutCheckListHead->nTimeStamp <= g_nAbsoluteTime) && (g_pTimeoutCheckListHead->bIsOverflow == g_bIs_g_nAbsoluteTimeOverFlow))
            {
                pWaitingToHandle = g_pTimeoutCheckListHead;
                g_pTimeoutCheckListHead = g_pTimeoutCheckListHead->pNextTimer;
                if(g_pTimeoutCheckListHead != NULL)
                    g_pTimeoutCheckListHead->pPreTimer = NULL;
                pWaitingToHandle->pNextTimer = NULL;
    
                pEarliestTimer = pWaitingToHandle;
                while(pEarliestTimer != NULL)
                {
                    pEarliestTimerPreHandle = pEarliestTimer;
                    pEarliestTimer = pEarliestTimer->pNextHandle;
                    pEarliestTimerPreHandle->pNextHandle = NULL;
                    pEarliestTimerPreHandle->pNextTimer = NULL;
                    pEarliestTimerPreHandle->pPreTimer = NULL;
                    pEarliestTimerPreHandle->pTimeoutCallbackfunction(pEarliestTimerPreHandle->pTimeoutCallbackParameter);
                    if(!(pEarliestTimerPreHandle->bIsSingleUse))
                        AddTimerToCheckList(pEarliestTimerPreHandle);
                }
            }
        }
    
        g_nAbsoluteTime++;
        if(g_nAbsoluteTime == 0)
            g_bIs_g_nAbsoluteTimeOverFlow = !g_bIs_g_nAbsoluteTimeOverFlow;
    
        return ;
    }
    
    void CancleAllTimerTask()
    {
        tMultiTimer* pEarliestTimer = NULL;
        tMultiTimer* pHandleTimer = NULL;
    
        while(g_pTimeoutCheckListHead != NULL)
        {
            pEarliestTimer = g_pTimeoutCheckListHead;
            g_pTimeoutCheckListHead = g_pTimeoutCheckListHead->pNextTimer;
    
            while(pEarliestTimer != NULL)
            {
                pHandleTimer = pEarliestTimer;
                pEarliestTimer = pEarliestTimer->pNextHandle;
    
                pHandleTimer->pNextHandle = NULL;
                pHandleTimer->pNextTimer = NULL;
                pHandleTimer->pPreTimer = NULL;
                pHandleTimer->bIsOverflow = false;
            }
        }
        g_bIs_g_nAbsoluteTimeOverFlow = false;
        g_nAbsoluteTime = 0;
        return;
    }
    
    void MultiTimerInit()
    {
        g_pTimeoutCheckListHead = NULL;
        g_bIs_g_nAbsoluteTimeOverFlow = false;
        g_nAbsoluteTime = 0;
        for(uint8_t index = 0; index < MAX_TIMER_UPPER_LIMIT; index++)
        {
            g_aSPPMultiTimer[index] = (tMultiTimer*)CMALLOC(sizeof(tMultiTimer));
            g_aSPPMultiTimer[index]->nTimerID = g_aTimerID[index];
            g_aSPPMultiTimer[index]->nInterval = g_aDefaultTimeout[index];
            g_aSPPMultiTimer[index]->nTimeStamp = 0;
            g_aSPPMultiTimer[index]->bIsSingleUse = true;
            g_aSPPMultiTimer[index]->bIsOverflow = false;
            g_aSPPMultiTimer[index]->pTimeoutCallbackfunction = NULL;
            g_aSPPMultiTimer[index]->pTimeoutCallbackParameter = NULL;
            g_aSPPMultiTimer[index]->pNextTimer = NULL;
            g_aSPPMultiTimer[index]->pPreTimer = NULL;
            g_aSPPMultiTimer[index]->pNextHandle = NULL;
        }
        /*  如果预先规定了一些定时器,这个时候可以初始化除时间戳以外的其他值  */
        //开启应答超时任务
        //OPEN_MULTITIMER_MANGMENT();
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294

    multiTimer.h

    #ifndef __MULTITIMER_H__
    #define __MULTITIMER_H__
    
    #define MAX_TIMER_UPPER_LIMIT   6
    
    #define TIMER_0                 0
    #define TIMER_1                 1       //timer ID
    #define TIMER_2                 2
    #define TIMER_3                 3
    #define TIMER_4                 4
    #define TIMER_5                 5
    
    #define CANCEL_MODE_IMMEDIATELY         0xf9
    #define CANCEL_MODE_AFTER_NEXT_TIMEOUT  0x9f
    
    typedef void TimeoutCallBack(void*);
    
    //========================================================
    //                      timer结构定义
    //========================================================
    typedef struct tMultiTimer
    {
        uint8_t nTimerID;       //
        uint32_t nInterval;     //定时时长
        uint32_t nTimeStamp;    //时间戳
        bool bIsSingleUse;      //是否单次使用
        bool bIsOverflow;       //用于解决计数溢出问题
        TimeoutCallBack *pTimeoutCallbackfunction;
        void* pTimeoutCallbackParameter;
    
        //双向链表指针
        struct tMultiTimer* pNextTimer;
        struct tMultiTimer* pPreTimer;
        //相同时间戳的下一个处理函数    这里可能会有隐藏的 bug,如果基础时间中断比较快,那么可能在处理多个同一时间节点的
        //回调函数的时候被下一次的中断打断,这里会引起时序错误,
        //解决方案有三种,
        //一是可以人为避免,不设置有公约数的定时时间,这样的话同一个时刻有多个定时任务的情况就小很多;
        //二是回调函数尽量少做事,快速退出定时处理函数;
        //三是另开一个线程,这个线程仅把回调函数放到一个队列中,另一个线程持续从队列中取回调函数执行,这个是没有问题的方案,但是需要支持多线程或者多任务,并且需要注意加锁
        struct tMultiTimer* pNextHandle;
    
    }tMultiTimer;
    
    //========================================================
    //               实现多定时任务的相关变量
    //========================================================
    
    tMultiTimer* g_pTimeoutCheckListHead;
    bool g_bIs_g_nAbsoluteTimeOverFlow;
    uint32_t g_nAbsoluteTime;
    
    //========================================================
    //                      外部接口
    //========================================================
    void MultiTimerInit();
    uint8_t SetTimer(uint8_t nTimerID,uint32_t nInterval,bool bIsSingleUse,TimeoutCallBack* pCallBackFunction,void* pCallBackParameter);
    uint8_t CancelTimerTask(uint8_t nTimerID,uint8_t nCancelMode);
    void CancleAllTimerTask();
    void SYSTimeoutHandler(int signo);
    #endif
  • 相关阅读:
    当函数中传递的参数数量不定时,获取传递给函数的参数信息
    redis分页摘抄
    redis分页
    返回数组中指定的一列,将键值作为元素键名array_column
    PHP操作Redis常用技巧总结
    php面试中的经典问题
    php 依赖注入的实现
    10分钟快速理解依赖注入
    PHP 依赖注入,依赖反转 (IOC-DI)
    PHP控制反转(IOC)和依赖注入(DI)
  • 原文地址:https://www.cnblogs.com/schips/p/11010326.html
Copyright © 2011-2022 走看看