高性能定时器
定时器的结构有多钟比如链表式,最小堆,时间轮的 在不同应用场景下使用哪种需要考虑效率和复杂度
这次我么那先先讲讲时间轮定时器,在linux内核里这种结构的定时器大量使用。
1.升序链表定时器
   
时间轮定时器
1.时间轮定时器有什么好处,或者说这种结构的定时器能决解什么问题?
在上面的升序链表定时器里,可以看到在添加一个定时器的时候,复杂度是O(n)
因为要保持有序性,所以的遍历链表插入到合适的位置。假设系统有大量的定时器(10W个)
使用升序链表型的就会有性能问题。这时时间轮定时器就会比较适合。

常用定时器实现算法复杂度 
实现方式 StartTimer StopTimer PerTickBookkeeping
基于链表 O(1)    O(n)    O(n)
基于排序链表 O(n)    O(1)    O(1)
基于最小堆 O(lgn)    O(1)    O(1)
基于时间轮 O(1)    O(1)    O(1)

如图:

假设有N个槽,时间轮已恒定速度顺时针转动,每转动一步槽指针就指向下一个槽,每转动一次的时间间隔叫做一个滴答间隔si,
这样转动一周的时间为 T = si*N ,每个槽都是一个链表。这样在插入定时器的时候可以直接计算出要放在那个槽。

假设在T时间后到期,insertslot = (curslot + (T/si)) % N,计算出了insertslot就可以在O(1)的复杂度里完成。

//下面是简单的时间轮定时器代码

class tw_timer;

struct client_data
{
    unsigned int uUin; //角色ID
    unsigned int utype; //建筑类型
    tw_timer* timer;
};

typedef void (*pFUNC)(client_data*);
class tw_timer
{
public:
    //rot轮转几圈定时器到期
    //ts 槽的索引
    tw_timer( int rot, int ts ,pFUNC TimeOutCall) 
    : next( NULL ), prev( NULL ), rotation( rot ), time_slot( ts )
    {
        TimeOutfunc = TimeOutCall;
    }

public:
    //轮转几圈定时器到期
    int rotation;
    // 槽的索引
    int time_slot;
    //到时后的回调函数
    //void (*cb_func)( client_data* );
    pFUNC TimeOutfunc;
    //自定义函数
    client_data* user_data;
    //链表的指针
    tw_timer* next;
    tw_timer* prev;
};

class time_wheel
{
public:
    time_wheel() : cur_slot( 0 ))
    {
        //获得服务器时间
        LastTickTime = GetCurTime(); 
        for( int i = 0; i < N; ++i )
        {
            slots[i] = NULL;
        }
    }
    ~time_wheel()
    {
        for( int i = 0; i < N; ++i )
        {
            tw_timer* tmp = slots[i];
            while( tmp )
            {
                slots[i] = tmp->next;
                delete tmp;
                tmp = slots[i];
            }
        }
    }
    tw_timer* add_timer( int timeout, pFUNC TimeOutCall)
    {
        if( timeout < 0 )
        {
            return NULL;
        }
        int ticks = 0;
        //最少要一个滴答间隔
        if( timeout < TI )
        {
            ticks = 1;
        }
        else
        {
            ticks = timeout / TI;
        }
        //rotation为0表示定时器到期
        int rotation = ticks / N;
        //计算槽索引
        int ts = ( cur_slot + ( ticks % N ) ) % N;
        tw_timer* timer = new tw_timer( rotation, ts ,TimeOutCall);
        //当前的槽上没有定时器就放在head位置,否则放在插入在head位置
        if( !slots[ts] )
        {
            printf( "add timer, rotation is %d, ts is %d, cur_slot is %d\n", rotation, ts, cur_slot );
            slots[ts] = timer;
        }
        else
        {
            timer->next = slots[ts];
            slots[ts]->prev = timer;
            slots[ts] = timer;
        }
        return timer;
    }
    //删除一个定时器,主要是链表的删除的操作
    void del_timer( tw_timer* timer )
    {
        if( !timer )
        {
            return;
        }
        int ts = timer->time_slot;
        if( timer == slots[ts] )
        {
            slots[ts] = slots[ts]->next;
            if( slots[ts] )
            {
                slots[ts]->prev = NULL;
            }
            delete timer;
        }
        else
        {
            timer->prev->next = timer->next;
            if( timer->next )
            {
                timer->next->prev = timer->prev;
            }
            delete timer;
        }
    }

//每一个滴答间隔调用一次tick函数 time为当前服务器时间
    void tick(unsigned int time)
    {
        //计算更新间隔进过了多少个滴答
        unsigned int Ticount = (time - LastTickTime)/TI; 
        tw_timer* tmp = slots[cur_slot];
        printf( "current slot is %d\n", cur_slot );
        for(int i = 0;i < Ticount; ++i)
        {
            while( tmp )
            {
                printf( "tick the timer once\n" );
                if( tmp->rotation > 0 )
                {
                    tmp->rotation--;
                    tmp = tmp->next;
                }
                else
                {
                    tmp->TimeOutfunc( tmp->user_data );
                    if( tmp == slots[cur_slot] )
                    {
                        printf( "delete header in cur_slot\n" );
                        slots[cur_slot] = tmp->next;
                        delete tmp;
                        if( slots[cur_slot] )
                        {
                            slots[cur_slot]->prev = NULL;
                        }
                        tmp = slots[cur_slot];
                    }
                    else
                    {
                        tmp->prev->next = tmp->next;
                        if( tmp->next )
                        {
                            tmp->next->prev = tmp->prev;
                        }
                        tw_timer* tmp2 = tmp->next;
                        delete tmp;
                        tmp = tmp2;
                    }
                }
            }
            //移动到下一个槽,时间轮是环所以需要%N
        cur_slot = ++cur_slot % N;
       }
        LastTickTime = time;
    }

private:
    //槽个数
    static const int N = 60;
    //滴答间隔(每移动一个槽的时间间隔)
    static const int TI = 1; 
    //时间轮
    tw_timer* slots[N];
    //当前槽索引
    int cur_slot;
    //最后更新
    unsigned int LastTickTime;
};

//假设在后台如何使用了

后台都会有一个主循环大概如下

bool update()
{
    while(!stopserver)
    {
        //读网络IO
        //读DB数据包
        //处理事件
        //处理定时器
        timewhel.tick();
        //处理逻辑
    }
  
}

//就在住循环里驱动我们的定时器,在调用tick函数

比如我们现在有这么个个需求,就是玩家可以建造各式各样的建筑,比如房子,兵营,田地等,被建造的建筑会在一定时间后才能完成,并通知给前台,这样就需要一个定时器

//建造人口房屋
void BuilderHouse(client_data* clietdata)
{
    //伪代码逻辑
    /*
    if (NULL == clietdata)
    {
        LOG("XXX");
        return;
    }
    
    CRole* pRole = FindRole(clietdata->uUin);
    if (NULL == pRole)
    {
         LOG("XXX");
         return;
    }
    //调用角色建造人口接口,处理后台逻辑
    pRole->BuilderHouse();

//通知给前台

Send(msg);
    */
}

//建造兵营
void BuilderCamp(client_data* clietdata)
{
    //同上
}

//建造田地
void BuilderField(client_data* clietdata)
{
    //同上
}

static time_wheel timewhel;

//假设玩家在游戏里场景里创建了一个房子,会执行下行代码

int CmdBuild()
{    
    //房子建造完成需要3分钟(180s) ,BuilderHouse为完成后的回调函数
    timewhel.add_timer(180,BuilderHouse);

游戏后台之高效定时器-时间轮相关推荐

  1. 高性能定时器--时间轮/多级时间轮

    运行原理 指针指向轮子上的一个槽,轮子以恒定的速度顺时针转动,每转动一步就指向下一个槽(虚线指针指向的槽),每次转动称为一个tick,一个tick的时间称为时间轮的槽间隔slot interval,即 ...

  2. 高性能定时器-------时间轮

    基于排序链表的定时器(https://blog.csdn.net/destory27/article/details/81748580)存在一个问题:添加定时器的效率偏低. 如图所示时间轮内,指针指向 ...

  3. Linux网络编程 | 高性能定时器 :时间轮、时间堆

    文章目录 时间轮 时间堆 在上一篇博客中我实现了一个基于排序链表的定时器容器,但是其存在一个缺点--随着定时器越来越多,添加定时器的效率也会越来越低. 而下面的两个高效定时器--时间轮.时间堆,会完美 ...

  4. 时间轮和时间堆管理定时器

    高性能定时器 时间轮 由于排序链表定时器容器有这样一个问题:添加定时器的效率偏低.而即将介绍的时间轮则解决了这个问题.一种简单的时间轮如下所示. 如图所示的时间轮内,指针指向轮子上的一个slot(槽) ...

  5. TimeWheel时间轮算法原理及实现(附源码)

    时间轮算法原理及实现 前言 1.时间轮核心 2.简单定时器 3.任务队列 4.优化任务队列 5.简单时间轮 6.多层时间轮 前言  在各种业务场景中,我们总是会需要一些定时进行一些操作,这些操作可能是 ...

  6. 网络编程 高性能定时器数据结构分析 | 时间轮 红黑树定时器性能分析 | 为什么要做用户态定时器

    为什么要用户态的定时器? 首先是为什么要做定时器,定时器的主要说的是我们的应用(业务?功能?总之有这个需求)要做一个定时的任务.其实如果不想为什么,好像是理所当然的.我写这个的时候,知乎有一个问题(L ...

  7. 基于libco的c++协程实现(时间轮定时器)

    在后端的开发中,定时器有很广泛的应用. 比如: 心跳检测 倒计时 游戏开发的技能冷却 redis的键值的有效期等等,都会使用到定时器. 定时器的实现数据结构选择 红黑树 对于增删查,时间复杂度为O(l ...

  8. 分级时间轮优化普通时间轮定时器(2):滴答式分层计时轮

    <实现较低的计时器粒度以重传TCP(RTO):时间轮算法如何减少开销> <分级时间轮优化普通时间轮定时器> Table of Contents 描述 新闻 用法 执照 资源 其 ...

  9. 分级时间轮优化普通时间轮定时器

    <实现较低的计时器粒度以重传TCP(RTO):时间轮算法如何减少开销> Table of Contents Ratas-分级计时器轮 (分级)计时器轮 单文件实现,无依赖性 限制单个时间步 ...

最新文章

  1. 【 MATLAB 】filter 函数介绍 之 Filter Data in Sections
  2. ABAP程序中的七大危险漏洞
  3. 8个关于SRT的误区
  4. 【ArcGIS风暴】ArcGIS平台上点云(.las)数据生成等高线方法案例精解
  5. zip unzip_zip和unzip上的Java要点
  6. 吸收Mockito的流利度
  7. 使用WebBrowser控件时在网页元素上绘制文本或其他自定义内容
  8. 简单的企业网站后台的实现之流程
  9. php过滤style,PHP过滤各种html标签
  10. 【gulp-sass】本地搭建sass开发环境
  11. linux下挂载ntfs分区错误解决方法
  12. BZOJ1096[ZJOI2007] 仓库建设
  13. Centos8 安装Tomcat
  14. mysql 5.7.20免安装_Windows下MySQL 5.7.20 免安装版配置
  15. 星际迷航传奇Star Trek for mac(太空冒险游戏)
  16. Transform.Forward和Vector3.Forward的正确使用方法
  17. 中后台管理系统之登录流程
  18. 解决tplink wn726 无线网卡在linux下不能使用的问题
  19. mac obs直播软件 无法输出音频解决办法
  20. 第二节:delay()延时实现LED灯的闪烁。

热门文章

  1. 1647:迷路(矩阵快速幂+矩阵点的拆分)
  2. php ean13,php生成EAN_13标准条形码实例_php实例
  3. [UnexpectedValueException] Your github oauth token for github.com contains invalid characters
  4. 数码相框(五、使用freetype库在LCD显示几行文字)
  5. 如何将PPT进行压缩?简单的方法介绍
  6. java 判断基本数据类型_判断(1分) Java语言中的数组元素只能是基本数据类型而不能为对象类型。...
  7. 2020.9.23 金山云后台开发岗笔试 2道编程
  8. 核苷酸(evolution)
  9. GBA的内存其实很大
  10. 计算机志愿者维修服务队,信息技术志愿者服务队