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