zoukankan      html  css  js  c++  java
  • 定时器

            在开发高性能服务器中,定时器总是不可或缺的。 常见的定时器实现三种,分别是:排序链表,最小堆,时间轮。 之前用的定时器是基于最小堆的,在定时器数量不多时可以使用, 目前公司用的框架中的定时器是基于简单时间轮的,但是为了支持大范围的时间,每个齿轮的所维护的链表为有序链表,每次插入时先mod出spoke,再从头遍历链表以便将定时器插入到合适位置, 所以本质上还是基于有序链表的。时间复杂度并未减少。

    应用场景分析: 下面就一个实际例子来说定时器的使用。

    场景: 客户端发起的网络请求,需要对每个请求做超时检查。

    方案1:一个定时器,一个mulimap<endtime, request>保存请求超时列表, 每次超时时检查mulimap。这样,请求的插入时间复杂度为O(lgn), 遍历和删除为O(1)。且需要额外的编码。

    方案2:一个请求一个定时器,如此便无需额外的开销来保存请求。已无需额外的编码,等待超时处理即可(请求的信息作为参数给定时器node保存)。时间复杂度为0。

    如果程序中的定时器数量比较少,基于最小堆的定时器一般可以满足需求,且实现简单。而对于方案2中的应用场景,对定时器的要求边比较高了。

    三种定时器算法复杂度分析:

    实现方式

    StartTimer

    StopTimer

    PerTickBookkeeping

    基于排序链表

    O(n)

    O(1)

    O(1)

    基于最小堆

    O(lgn)

    O(1)

    O(1)

    基于时间轮

    O(1)

    O(1)

    O(1)

     

    实现分析:

    排序链表:实现比较简单,不多讲。

    最小堆: 用c++现成的multimap保存便可以。实现亦比较简单。

    时间轮: 时间轮的实现 有 简单时间轮(一个时间轮),分级时间轮。

         简单时间轮: 一个齿轮,每个齿轮保存一个超时的node链表。一个齿轮表示一个时间刻度,比如钟表里面一小格代表一秒,钟表的秒针每次跳一格。假设一个刻度代表10ms,则2^32 个格子可表示1.36年,2^16个格子可表示10.9分钟。当要表示的时间范围较大时,空间复杂度会大幅增加。

       分级时间轮: 类似于水表,当小轮子里的指针转动满一圈后,上一级轮子的指针进一格。  采用五个轮子每个轮子为一个简单时间轮,大小分别为 2^8, 2^6, 2^6, 2^6, 2^6,所需空间:2^8 + 2^6 + 2^6 + 2^6 + 2^6 = 512, 可表示的范围为 0  --  2^8 * 2^6 * 2^6* 2^6* 2^6 = 2^32 。

    Linux底层的定时器实现便是基于此。下图为引用的linux内核定时器的实现。

     

    基于分级时间轮的C++实现: 已经过测试

         所需数据结构:

         每个spoke维护的node链表为一个环,如此可以简化插入删除的操作。spoke->next为node链表中第一个节点,prev为node连接的最后一个节点。

    复制代码
    #define GRANULARITY 10 //10ms
    #define WHEEL_BITS1 8
    #define WHEEL_BITS2 6
    #define WHEEL_SIZE1 (1 << WHEEL_BITS1) //256
    #define WHEEL_SIZE2 (1 << WHEEL_BITS2) //64
    #define WHEEL_MASK1 (WHEEL_SIZE1 - 1)
    #define WHEEL_MASK2 (WHEEL_SIZE2 - 1)
    #define WHEEL_NUM 5
    
    
    typedef struct stNodeLink {
        stNodeLink *prev;
        stNodeLink *next;
        stNodeLink() {prev = next = this;} //circle
    }SNodeLink;
    typedef struct stTimerNode {
        SNodeLink link;
        uint64_t dead_time;
        CThreadTimer *timer;
        stTimerNode(CThreadTimer *t, uint64_t dt) :  dead_time(dt), timer(t) {}
    }STimerNode;
    typedef struct stWheel {
        SNodeLink *spokes;
        uint32_t size;
        uint32_t spokeindex;
        stWheel(uint32_t n) : size(n), spokeindex(0){ 
            spokes = new SNodeLink[n];
        }
        ~stWheel() { 
            /** clean **/
        }
    }SWheel;
    
    SWheel *wheels_[WHEEL_NUM];
    复制代码

         插入定时器:根据超时范围选择轮子,再通过mod/n求出要插入的spoke位置。

    复制代码
    void CTimerManager::AddTimerNode(uint32_t milseconds, STimerNode *node) {
        SNodeLink *spoke = NULL;
        uint32_t interval = milseconds / GRANULARITY;
        uint32_t threshold1 = WHEEL_SIZE1;
        uint32_t threshold2 = 1 << (WHEEL_BITS1 + WHEEL_BITS2);
        uint32_t threshold3 = 1 << (WHEEL_BITS1 + 2 * WHEEL_BITS2);
        uint32_t threshold4 = 1 << (WHEEL_BITS1 + 3 * WHEEL_BITS2);
        
        if (interval < threshold1) {
            uint32_t index = (interval + wheels_[0]->spokeindex) & WHEEL_MASK1;
            spoke = wheels_[0]->spokes + index;
        } else if (interval < threshold2) {
            uint32_t index = ((interval - threshold1 + wheels_[1]->spokeindex * threshold1) >> WHEEL_BITS1) & WHEEL_MASK2;
            spoke = wheels_[1]->spokes + index;
        } else if (interval < threshold3) {
            uint32_t index = ((interval - threshold2 + wheels_[1]->spokeindex * threshold2) >> (WHEEL_BITS1 + WHEEL_BITS2)) & WHEEL_MASK2;
            spoke = wheels_[2]->spokes + index;
        } else if (interval < threshold4) {
            uint32_t index = ((interval - threshold3 + wheels_[1]->spokeindex * threshold3) >> (WHEEL_BITS1 + 2 * WHEEL_BITS2)) & WHEEL_MASK2;
            spoke = wheels_[3]->spokes + index;
        } else {
            uint32_t index = ((interval - threshold4 + wheels_[1]->spokeindex * threshold4) >> (WHEEL_BITS1 + 3 * WHEEL_BITS2)) & WHEEL_MASK2;
            spoke = wheels_[4]->spokes + index;
        }
        SNodeLink *nodelink = &(node->link);
        nodelink->prev = spoke->prev;
        spoke->prev->next = nodelink;
        nodelink->next = spoke;
        spoke->prev = nodelink;
    }
    复制代码

         删除定时器:实际上是删除一个双向链表的元素。只需修改其前后节点的prev next指针指向而已。

    复制代码
    void CTimerManager::RemoveTimer(STimerNode* node) {
        SNodeLink *nodelink = &(node->link);
        if (nodelink->prev) {
            nodelink->prev->next = nodelink->next;
        }
        if (nodelink->next) {
            nodelink->next->prev = nodelink->prev;
        }
        nodelink->prev = nodelink->next = NULL;
        
        delete node;
    }
    复制代码

         定时间超时检查:

    复制代码
    void CTimerManager::DetectTimerList() {
        uint64_t now = GetCurrentMillisec();
        uint32_t loopnum = now > checktime_ ? (now - checktime_) / GRANULARITY : 0;
        
        SWheel *wheel =  wheels_[0];
        for (uint32_t i = 0; i < loopnum; ++i) {
            SNodeLink *spoke = wheel->spokes + wheel->spokeindex;
            SNodeLink *link = spoke->next;
            while (link != spoke) {
                STimerNode *node = (STimerNode *)link;
                link->prev->next = link->next;
                link->next->prev = link->prev;
                link = node->link.next;
                AddToReadyNode(node);
            }
            if (++(wheel->spokeindex) >= wheel->size) {
                wheel->spokeindex = 0;
                Cascade(1);
            }
            checktime_ += GRANULARITY;
        }
        DoTimeOutCallBack();
    }
    复制代码

         降级:

    复制代码
    uint32_t CTimerManager::Cascade(uint32_t wheelindex) {
        if (wheelindex < 1 || wheelindex >= WHEEL_NUM) {
            return 0;
        }
        SWheel *wheel =  wheels_[wheelindex];
        int casnum = 0;
        uint64_t now = GetCurrentMillisec();
        SNodeLink *spoke = wheel->spokes + (wheel->spokeindex++);
        SNodeLink *link = spoke->next;
        spoke->next = spoke->prev = spoke;
        while (link != spoke) {
            STimerNode *node = (STimerNode *)link;
            link = node->link.next;
            if (node->dead_time <= now) {
                AddToReadyNode(node);
            } else {
                uint32_t milseconds = node->dead_time - now;
                AddTimerNode(milseconds, node);
                ++casnum;
            }
            
        }
        
        if (wheel->spokeindex >= wheel->size) {
            wheel->spokeindex = 0;
            casnum += Cascade(++wheelindex);
        }
        return casnum;
    }
    复制代码
  • 相关阅读:
    正则表达式判断手机号是否11位
    php-流程管理(发起流程和审核流程)
    php-流程管理(新建流程)(php+Ajax)
    php-人员权限管理(RBAC)
    状态压缩DP
    树形DP
    Broken Keyboard UVA 11988 数组实现链表
    Trees in a Wood UVA
    POJ1061 青蛙的约会 拓展欧几里得
    UVA10215The Largest/Smallest Box(小数精度)
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3759833.html
Copyright © 2011-2022 走看看