時間輪


老早之前就聽說時間輪算法特別高效,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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM