zoukankan      html  css  js  c++  java
  • 时间轮

    老早之前就听说时间轮算法特别高效,Linux内核都用的它,这两天抽空实现了遍……嗯,被差一bug搞死(~ ̄▽ ̄~) 啊哈

    网上扣来的图,原理好懂:轮子里的每格代表一小段时间(精度),连起来就能表示时间点了(我去年买了个表),格子内含链表,中存回调函数;时间指针每次转动一格,指向某格时,取出链表里的回调函数依次执行,后清空链表,等待下一次转动。

    加入节点逻辑也简单:在轮子可表示的时间范围内(格子数*格子精度),配合当前时针位置,对格子总数取余,即得本节点需放哪个格子。

    进一步为扩大时间轮的表示范围,使用分级方式,跟时分秒一样,上一级转一圈,下一级动一格。

    对吧,很容易理解……然后coding是另一码事(╯‵□′)╯︵┴─┴

    首先,最重要的,数据结构:用了环形链表,方便增删。

    struct NodeLink {
        NodeLink* prev;
        NodeLink* next;
        NodeLink() { prev = next = this; } //circle
    };
    struct stWheel {
        NodeLink* slots; //每个slot维护的node链表为一个环,slot->next为node链表中第一个节点,prev为node的最后一个节点
        const uint32 size;
        uint32 slotIdx;
        stWheel(uint32 n) : size(n), slotIdx(0){ slots = new NodeLink[size]; }
        ~stWheel() {
            if (slots) {
                for (uint32 j = 0; j < size; ++j) {
                    NodeLink* link = (slots + j)->next;
                    while (link != slots + j) {
                        TimerNode* node = (TimerNode*)link;
                        link = node->link.next;
                        delete node;
                    }
                }
                delete[]slots;
            }
        }
    };

    具体时间节点的数据结构如下:

    struct TimerNode {
        Pool_Obj_Define(TimerNode, 32) //内存池声明,不含数据
        NodeLink link; //must in the head
        uint32 timeDead;
        uint32 interval; //间隔多久
        int loop;        //总共循环多久
        std::function<void()> func;
    };

    TimeNode里保存上下关系,stWheel的NodeLink辅助用的,环状链表的头,没实际数据,用以记录首尾TimeNode。

    核心代码如下:

    ——增删节点——

    void CTimerMgr::_AddTimerNode(uint32 milseconds, TimerNode* node) {
        NodeLink* slot = NULL;
        uint32 tickCnt = milseconds / TIME_TICK_LEN;
    
        if (tickCnt < WHEEL_CAP[0]) {
            uint32 index = (_wheels[0]->slotIdx + tickCnt) & (WHEEL_SIZE[0] - 1); //2的N次幂位操作取余
            slot = _wheels[0]->slots + index;
        } else {
            for (int i = 1; i < WHEEL_NUM; ++i) {
                if (tickCnt < WHEEL_CAP[i]) {
                    uint32 preCap = WHEEL_CAP[i - 1]; //上一级总容量即为本级的一格容量
                    uint32 index = (_wheels[i]->slotIdx + tickCnt / preCap - 1) & (WHEEL_SIZE[i] - 1); //勿忘-1
                    slot = _wheels[i]->slots + index;
                    break;
                }
            }
        }
        NodeLink* link = &(node->link);
        link->prev = slot->prev; //插入格子的prev位置(尾节点)
        link->prev->next = link;
        link->next = slot;
        slot->prev = link;
    }
    void CTimerMgr::RemoveTimer(TimerNode* node) {
        LOG_TRACK("node[%p], timeDead[%lld]", node, node->timeDead);
        NodeLink* link = &(node->link);
        if (link->prev) {
            link->prev->next = link->next;
        }
        if (link->next) {
            link->next->prev = link->prev;
        }
        link->prev = link->next = NULL;
    
        delete node;
    }

    ——轮子启动——

    void CTimerMgr::CheckTimerList(const uint32 timenow) {
        uint32 tickCnt = timenow > _checkTime ? (timenow - _checkTime) / TIME_TICK_LEN : 0;
        //if (tickCnt) Printf();
        for (uint32 i = 0; i < tickCnt; ++i) { //扫过的slot均超时
            stWheel* wheel = _wheels[0];
            NodeLink* slot = wheel->slots + wheel->slotIdx;
            NodeLink* link = slot->next;
            slot->next = slot->prev = slot; //清空当前格子
            while (link != slot) {            //环形链表遍历
                TimerNode* node = (TimerNode*)link;
                link = node->link.next; //得放在前面,后续函数调用,可能会更改node的链接关系
                AddToReadyNode(node);
            }
            if (++(wheel->slotIdx) >= wheel->size) {
                wheel->slotIdx = 0;
                Cascade(1, timenow); //跳级
            }
            _checkTime += TIME_TICK_LEN;
        }
        DoTimeOutCallBack();
    }
    uint32 CTimerMgr::Cascade(uint32 wheelIdx, const uint32 timenow) {
        if (wheelIdx < 1 || wheelIdx >= WHEEL_NUM) {
            return 0;
        }
        int casCnt = 0;
        stWheel* wheel = _wheels[wheelIdx];
        NodeLink* slot = wheel->slots + wheel->slotIdx;
        NodeLink* link = slot->next;
        slot->next = slot->prev = slot; //清空当前格子
        while (link != slot) {
            TimerNode* node = (TimerNode*)link;
            link = node->link.next;
            if (node->timeDead <= timenow) {
                AddToReadyNode(node);
            } else {
                _AddTimerNode(node->timeDead - timenow, node); //本级精度下已超时,精度提升,重新加一遍
                ++casCnt;
                LOG_TRACK("wheelIdx[%u], link[%p], milseconds[%u]", wheelIdx, link, node->timeDead - timenow);
            }
        }
        if (++(wheel->slotIdx) >= wheel->size) {
            wheel->slotIdx = 0;
            casCnt += Cascade(++wheelIdx, timenow);
        }
        return casCnt;
    }

    那么问题来了:大于,大于等于,边界,减一……搞错几多次 ○(* ̄︶ ̄*)○ 吃饱睡好

    源码地址:https://github.com/3workman/Tools/tree/master/src/Timer

  • 相关阅读:
    hdoj2159【二位费用背包】
    POJ2367【拓扑排序】
    POJ2371【水题】
    POJ2369【循环节】
    POJ2370【水题】
    POJ2365【几何】
    POJ2366【二分】
    POJ1276【多重背包】
    瞎说一波3种基本背包问题【希望巨巨们指出错误】
    Codeforces 550B 【暴力】
  • 原文地址:https://www.cnblogs.com/3workman/p/6063819.html
Copyright © 2011-2022 走看看