时间轮

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

时间: 2024-12-27 00:06:46

时间轮的相关文章

高性能定时器时间轮的探究

时间轮的概念 关于定时器有很多种,有基于升序的定时器时间链表,但是这种链表存在效率的不足,就是当插入定时器的时候时间复杂度是O(n).今天,我们来认识一下高性能定时器时间轮. 如上图所示,就是一个时间轮的基本轮廓.一个轮子上有很多槽slot,每一个槽指向一个定时器链表,这个链表是无序的.时间轮每转动一步就会指向下一个槽,其实也可以理解为一个滴答时间成为时间轮的槽间隔si (slot interval).它实际上就是心跳时间.如果该时间轮有n个槽,因此它运转一周的时间是n*si. 如果现在指针指向

Linux C++ 实现时间轮 优化超时检测机制

参考资料: http://www.ijilei.com/8357 https://www.zhihu.com/question/38427301 https://www.ibm.com/developerworks/cn/linux/l-cn-timers/ http://www.cnblogs.com/processakai/archive/2012/04/11/2442294.html 思路和代码的编写主要是参考的csdn上的一个java的代码 http://blog.csdn.net/mi

时间轮(TimeWheel)的设计与实现

一.前言 由于工作的需要,得实现一个用于控制事件超时抛弃的时间轮,由于这是一个相对独立的接口,就总结分享一下. 首先看下需求,此时间轮需要具备下面几个功能: 1)能添加事件,同时附上其超时时间: 2)如果事件正常执行结束,可以显示将其从时间轮上剔除掉,而不需要等时间轮自动移除: 3)如果事件到了设定的超时时间还没执行完,则时间轮需将其剔除掉,并发送一个超时的消息给系统. 基于这样的需求,下面就进行相应的设计和实现. 二.时间轮的设计 基于前面的需求,可以抽象出两个实体来:时钟和槽,其中时钟去负责

记录——时间轮定时器(lua 实现)

http://www.cnblogs.com/mmc1206x/p/6849172.html 很长一段时间里,我错误的认识了定时器.无意中,我发现了“时间轮”这个名词,让我对定时器有了新的看法. 我错误的认为,定时器只需要一个 tick 队列,按指定的时间周期遍历队列,检查 tick 倒计时满足触发条件就触发回调. tick 定义如下: 1 struct Tick { 2 int_t n; 3 func_t func; 4 }; 遍历触发实现如下: 1 void Update() 2 { 3 f

时间轮算法

问题引入:游戏里面每个Player身上有很多buffs,在每一个tick(最小时间段)都要去检查buff里面的每一个buff是不是过期,产生的效果如何,造成在每个tick里面都去遍历一个长list,明显很不好. 怎么优化? 1.原始模型:    buff的状态在每一个tick里面都要更新!可以想象指针每移动一下,都会非常沉重地拖着所有的BuffList,好可怕-- 2. 优化模型1:   我们要避免的是:原始模型在每一个tick里面都要遍历List,那么我们试下以Times为key,在加入buf

浅析时间轮定时器

前言: 最早是看到skynet群里边有人问如何取消定时器的问题,那时候正好在研读skynet代码,于是决定试试.但是最终只在lua层面实现了一个伪取消定时器的方案,而且还是不是优解. 云风说从c层面取消定时器的开销要大于从lua层面取消的开销,当时不知道为什么. 最近研读了云风实现的时间轮定时器代码(看着相当费劲啊),  过程中网上搜了很多资料,但大部分没能帮助我有个更好的理解,所以打算从写篇文章,希望能帮助像我一样的newbee, 更好的理解时间轮定时器. 这里不讲定时器的进化过程,只讲时间轮

原 荐 简单说说Kafka中的时间轮算法

零.时间轮定义 简单说说时间轮吧,它是一个高效的延时队列,或者说定时器.实际上现在网上对于时间轮算法的解释很多,定义也很全,这里引用一下 朱小厮博客 里出现的定义: 参考下图,Kafka中的时间轮(TimingWheel)是一个存储定时任务的环形队列,底层采用数组实现,数组中的每个元素可以存放一个定时任务列表(TimerTaskList).TimerTaskList是一个环形的双向链表,链表中的每一项表示的都是定时任务项(TimerTaskEntry),其中封装了真正的定时任务TimerTask

Linux时间子系统之八:动态时钟框架(CONFIG_NO_HZ、tickless)

在前面章节的讨论中,我们一直基于一个假设:Linux中的时钟事件都是由一个周期时钟提供,不管系统中的clock_event_device是工作于周期触发模式,还是工作于单触发模式,也不管定时器系统是工作于低分辨率模式,还是高精度模式,内核都竭尽所能,用不同的方式提供周期时钟,以产生定期的tick事件,tick事件或者用于全局的时间管理(jiffies和时间的更新),或者用于本地cpu的进程统计.时间轮定时器框架等等.周期性时钟虽然简单有效,但是也带来了一些缺点,尤其在系统的功耗上,因为就算系统目

Linux时间子系统之七:定时器的应用--msleep(),hrtimer_nanosleep()

我们已经在前面几章介绍了低分辨率定时器和高精度定时器的实现原理,内核为了方便其它子系统,在时间子系统中提供了一些用于延时或调度的API,例如msleep,hrtimer_nanosleep等等,这些API基于低分辨率定时器或高精度定时器来实现,本章的内容就是讨论这些方便.好用的API是如何利用定时器系统来完成所需的功能的. /**************************************************************************************