时间轮
老早之前就听说时间轮算法特别高效,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