utils 定时器 (一) 多级时间轮

游戏人生:1时钟和定时器

utils 定时器 (一) 多级时间轮
utils 定时器 (二) 链表
utils 定时器 (三) 最小堆
utils 定时器 (四) 红黑树

小到游戏各种活动的定时开启,大到游戏本身就是处于一个大的主循环中,每个tick(时钟周期)做固定的事情,游戏的运行离不开时钟和定时器。

时间轮算法(Timing-Wheel):类比时钟的24时 60分 60秒的3个度量,游戏里的32位Tick可以分为 6 6 6 6 8五个度量

1 1 1 1 1 1 |1 1 1 1 1 1 |1 1 1 1 1 1 |1 1 1 1 1 1 |1 1 1 1 1 1 1 1|
5级          |4           |3          |2           |1              

随着时间的推进,tick会变化,00000001 - 11111111 即从1 到 255,经历了256个tick,其中每个tick都会有对应的事件发生。至于1s 等于多少个tick是可以人为设定的,一般游戏1s=50tick,即服务器每20ms完成一次主循环。

其中把每个tick将要发生的事件用list串联起来
相比红黑树、最小堆、链表等实现,时间轮定时器的操作粒度非常小,o(1),删除的话lazy delete可以达到o(1)

基本结构

/*
* per-CPU timer vector definitions:
*/
#define TVR_BITS (8)                  //1级时间轮8位
#define TVN_BITS (6)                  //n级时间轮6位 
#define TVR_SIZE (1 << TVR_BITS)   //1级时间轮的刻度256格
#define TVN_SIZE (1 << TVN_BITS)   //n级时间轮的刻度64格
#define TVR_MASK (TVR_SIZE - 1)    //1级掩码:11111111
#define TVN_MASK (TVN_SIZE - 1)    //n级掩码:111111

typedef unsigned long long mid_t;
typedef std::list<mid_t> timer_list;
typedef std::list<mid_t>::iterator timer_list_itr;

struct TVEC_TOOR_T
{
    timer_list vec[TVR_SIZE]; //256条链表
};

struct TVEC_T
{
    timer_list vec[TVN_SIZE]; //64
};

所以某一时刻的服务器时钟看上去是这样的。轮子级别越高,越泛泛。

第一级时间轮
1      do1-->do2-->do3-->      XX00000001tick要做的事
2      doa-->dob-->doc-->      XX00000010tick要做的事
...                            当前tick
255    doA-->doB-->doC-->      
__________
第二级时间轮
1      do1-->do2-->do3-->      当前tick的256-256*2个tick后需要做的事
2      doa-->dob-->doc--> 
...    
63     doA-->doB-->doC-->
__________
......
__________
第五级时间轮
1
2
...
63

游戏GM指令调整时间的需求,使得服务器时间和自然时间有差别

/* 定义时间轮类
* 在此先明确两个概念
* [自然时间]:外部系统的时间,通过time()获得
* [服务器时间]:服务器维护的系统时间,通过配置文件调整 
*/
struct TIMER_VEC_BASE
{
    int32 cur_tick;        // 当前时钟节拍,若小于get_current_tick(),则++,每加一次查找对应hash项
    s64 start_sec;          // 服务器启动的[自然时间](单位秒)
    s64 deviation_sec;      // 服务器启动的[服务器时间]和[自然时间]之间的差值(单位秒)
    s64 time_sec;           // 当前[自然时间](单位秒)
    s64 time_usec;          // 当前[自然时间](单位微秒)

    TVEC_TOOR_T tv1;     // 5级hash表
    TVEC_T tv2;
    TVEC_T tv3;
    TVEC_T tv4;
    TVEC_T tv5;
    s32 num;                // 当前定时器数目

    TIMEOUTFUNC timeout_funs[TIMEOUT_END] = { nullptr };
}

//list存放回调函数的节点 | 即tick时间后,要做的事情,需要该节点告诉我们
typedef void(*TIMEOUTFUNC)(void *data, size_t data_len); // 回调函数
struct TIMER_HANDLE
{
    tick_t expires;     // 超时tick
    s32 interval;       // 间隔时间
    s32 repeats;        // 执行次数
    bool forever;       // 是否永久执行

    s32 funcid;         // 回调函数id
    //std::string timeout_func_name;  //  回调函数名
    TIMEOUTFUNC timeout_func;         //  回调函数指针
    char cb_data[TIMER_CB_DATA_MAX_LEN];
    s32 data_len;

    s32 list_index[2];  // 存放list信息,记录出于时间轮位置
    timer_list *_list;      // 回指指针,用于删除

    mid_t this_mid;     // mempool id号
    bool isrunning;     // 是否正在被处理,防止超时处理中删除自己
};

初始化时间轮类

static TIMER_VEC_BASE g_timer_base;
int timer_init(int max_timer_num)
{
    int deviation_sec = 0; //偏移时间,服务器时间快一分钟即60
    
    struct timeval tv;
    gettimeofday(&tv, 0);//c库函数,返回微妙级别的时间
    g_timer_base.start_sec        = tv.tv_sec;
    g_timer_base.deviation_sec    = deviation_sec;
    g_timer_base.time_sec         = tv.tv_sec;
    g_timer_base.time_usec        = tv.tv_sec * 1000000 + tv.tv_usec;
    g_timer_base.cur_tick         = sec_to_tick(deviation_sec); //服务器时钟tick 从0开始算
    
    // 把1-5级时间轮上的的256+64*4条链表统统清零
    _init_tvr (&g_timer_base.tv1);
    _init_tvn (&g_timer_base.tv2);
    _init_tvn (&g_timer_base.tv3);
    _init_tvn (&g_timer_base.tv4);
    _init_tvn (&g_timer_base.tv5);
    
    g_timer_base.num = 0;
    return 0;
}

添加定时器

超时tick-当前tick得到时间间隔,判断时间轮级别
超时tick即绝对时间通过位操作确定是对应时间轮的第几条链表(位操作:&掩码)
掩码其实时利用了 x%(2 ^n)=x&(2 ^n -1)的性质,与运算要比取余快很多,所以tick的长度也大多为64 256 等指数

/**
@brief 加入定时器
@param[in] interval -- 间隔时间(以毫秒为单位)
@param[in] handler_func -- 回调函数指针
@param[in] cb_data -- 回调参数数据
@param[in] cb_len -- 回调参数数据长度 (最长不能超过TIME_CB_DATA_MAX_LEN)
@param[in] repeats -- 执行次数,TIMER_RUN_FOREVER(0)-永久执行
@retval INVALID_MID -- 失败
@retval 其他-- timer id,用于今后删除
*/
mid_t _add_timer_navi(int interval, s32 repeats, TIMEOUTFUNC handler_func, void *data, s32 data_len)
{
    START_FUNC_PROFILER(_type_add_timer);
    mid_t id;
    TIMER_HANDLE *handle = nullptr;

    if (!_add_timer_valid(interval, repeats, handler_func, data, data_len, TIMER_CB_DATA_MAX_LEN)) //检验有效性
        return INVALID_MID;

    // 从内存池分配TIMER_HANDLE,因为使用list存放回调函数的节点,list只记录一个内存池中的id
    id = memunit_alloc(MEM_DATA_TYPE_TIMER_HDNLE);
    handle = (TIMER_HANDLE *)memunit_get(id);
    if (nullptr == handle)
    {
        ADD_TIMER_LOG(error_log, "timer handle alloc failed");
        return INVALID_MID;
    }

    ++(g_timer_base.num);

    // 初始化
    // 考虑是使用cur_tick还是使用get_tick_by_time
    handle->expires = _get_expires_time(interval); //时钟当前tick + interval对应的tick
    handle->interval = interval;
    handle->repeats = repeats;
    handle->forever = (repeats == TIMER_RUN_FOREVER);

    handle->funcid = 0;
    handle->timeout_func = handler_func;
    if (data != nullptr) memcpy(handle->cb_data, (char *)data, data_len);
    handle->data_len = data_len;

    handle->this_mid = id;
    handle->isrunning = false;

    int ret = _internal_add_timer(handle);
    if (ret != 0)
    {
        ADD_TIMER_LOG(error_log, "internal add timer failed");
        memunit_free(id);
        return INVALID_MID;
    }

    return id;
}

// 根据超时tick-当前tick得到时间间隔,判断时间轮级别
// 超时tick通过位操作确定是对应时间轮的第几条链表(位操作+取掩码)
static int _internal_add_timer(struct TIMER_HANDLE *handle)
{
    if (handle->expires < g_timer_base.cur_tick)
    {
        error_log("timer %d expires %d invalid,cur_tick %d,interval %d",
            handle->funcid, handle->expires, g_timer_base.cur_tick, handle->interval);
        assert_retval(0, BGERR_INVALID_ARG);
    }

    s32 tv, pos_in_tv;

    _get_timer_list_index(handle->expires, pos_in_tv, tv);

    /*
    * Timers are FIFO:
    */

    handle->list_index[0] = tv;
    handle->list_index[1] = pos_in_tv;

    _timer_set_list(handle);
    handle->_list->push_back(handle->this_mid); //最末尾加入  //do1-->do2-->do3-->do? (下1个tick要做的)
    return 0;
}

static void _get_timer_list_index(tick_t expires, int& i, int& j)
{
    assert_retnone(expires>=g_timer_base.cur_tick);

    u64 idx = expires - g_timer_base.cur_tick;// 当前tick的后idx个tick
    if (idx < TVR_SIZE)
    {
        i = expires & TVR_MASK;//取1--7位,判断是1-255中的哪条链表
        j = 1;
    }
    else if (idx < 1 << (TVR_BITS + TVN_BITS))
    {
        i = (expires >> TVR_BITS) & TVN_MASK;//取8--13位
        j = 2;
    }
    else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS))
    {
        i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;//取14--19位
        j = 3;
    }
    else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS))
    {
        i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;//取20-25位
        j = 4;
    }
    else
    {
        /* If the timeout is larger than (1 << (TVR_BITS + 3 * TVN_BITS) on 64-bit
         * architectures then we use the maximum timeout:
         */
        i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;//取26-31位
        j = 5;
    }
}

static int _timer_set_list(struct TIMER_HANDLE *handle)
{
    switch (handle->list_index[0]) {
    case 1 :    handle->_list = g_timer_base.tv1.vec + handle->list_index[1];   break;
    case 2 :    handle->_list = g_timer_base.tv2.vec + handle->list_index[1];   break;
    case 3 :    handle->_list = g_timer_base.tv3.vec + handle->list_index[1];   break;
    case 4 :    handle->_list = g_timer_base.tv4.vec + handle->list_index[1];   break;
    case 5 :    handle->_list = g_timer_base.tv5.vec + handle->list_index[1];   break;
    default :   assert_retval(0, BGERR_ASSERT_DEFAULT);
    }

    return 0;
}

删除定时器

int del_timer(mid_t timerid)
{
    TIMER_HANDLE *handle;
    
    handle = (TIMER_HANDLE *)memunit_get(timerid);
    if (nullptr == handle)
    {
        debug_log ("invalid id %llu", mid_to_u64(timerid));
        return BGERR_NOT_FOUND;
    }

    // 如果正在处理,则处理完后删除
    if (handle->isrunning)
    {
        handle->repeats = 0;
        handle->forever = false;
        return 0;
    }

    // 定时器解链,list remove效率很低,需要注意
    handle->_list->remove(handle->this_mid);

    // 释放资源
    memunit_free(handle->this_mid);

    --g_timer_base.num;

    return 0;
}

时间轮步进

之前总是说某一时刻的时间轮状态,现在需要的是驱动时间轮动起来。

按照10进制来分析:

1tick时添加11tick的定时器a,因为间隔不<10tick,所以要添加到二级时间轮1号链表
1tick时添加21tick的定时器b,因为间隔不<10tick,所以要添加到二级时间轮2号链表
9tick时添加11tick的定时器c,因为间隔<10tick,所以要添加到一级时间轮1号链表
9tick时添加22tick的定时器d,因为间隔不<10tick,所以要添加到二级时间轮2号链表

10tick到来时,要将二级时间轮1号链表迁移到1级时间轮中,a,c都迁移到一级时间轮1号链表中

19tick时添加20tick的定时器e,因为间隔<10tick,所以要添加到一级时间轮的0号链表
PS: 19tick时的任何定时任务无法添加到二级时间轮的1号链表中,因为10tick< 时间间隔 <100tick时,tick>19至少也是2x,只能添加到2号链表中了

20tick到来时,要将二级时间轮2号链表迁移到1级时间轮中,b被迁移到一级时间轮的1号链表,d被迁移到一级时间轮的2号链表,执行e

可知相同tick的任务,在被执行前不一定都在同一条链表上,所以在进位发生时,需要把N级时间轮的链表迁移到更低级的时间轮中

void run_timer()
{

    while (get_tick_by_time() >= g_timer_base.cur_tick)
    {
        u32 cur_tick_prev = g_timer_base.cur_tick;
        __run_timers();
        u32 cur_tick_next = g_timer_base.cur_tick;
        assert_noeffect(cur_tick_prev < cur_tick_next);
    }
}

static inline void __run_timers()
{
    struct TIMER_HANDLE *handle;
    mid_t id;
    timer_list *list;
    s32 entry_index;
    TIMEOUTFUNC func;

    /**
     * Cascade timers:
    **/
    entry_index = g_timer_base.cur_tick & TVR_MASK;
    if ( !entry_index                               &&
        (!_cascade(&g_timer_base.tv2, INDEX(0))) &&
        (!_cascade(&g_timer_base.tv3, INDEX(1))) &&
         !_cascade(&g_timer_base.tv4, INDEX(2)))
        
        _cascade(&g_timer_base.tv5, INDEX(3));

    // 执行超时定时器链表
    list = g_timer_base.tv1.vec + entry_index;
    timer_list_itr itr;
    while (1)
    {
        if (list->empty())
        {
            break;
        }

        itr = list->begin();
        id = *itr;
        assert_retnone(mid_is_valid(id));

        handle = (TIMER_HANDLE *)memunit_get(id);
        assert_retnone(handle);
        assert_retnone(handle->_list == list);

        // 运行定时器
        func = handle->timeout_func;
        assert_retnone(func);

        if (handle->repeats > 0)
        {
            handle->repeats--;
        }

        if (!handle->isrunning)
        {
            handle->isrunning = true;
            {
                __run_one_timer(func, handle);
            }
        }

        handle->isrunning = false;

        // 根据策略,是否继续设置定时器
        if (handle->repeats > 0 || handle->forever)
        {
            // 定时器解链,先删除在加到对应的时间轮节点里面
            handle->_list->erase(itr);

            // 重新加入
            handle->expires = _get_expires_time(handle->interval);
            int ret = _internal_add_timer(handle);
            assert_noeffect(ret == 0);
        }
        else
        {
            del_timer(handle->this_mid);
        }
    }


    // 看下一个单位有没有超时
    ++g_timer_base.cur_tick;
}

// 进位操作
static s32 _cascade(TVEC_T *tv, s32 entry_index)
{
    /* cascade all the timers from tv up one level */
    timer_list *list;
    struct TIMER_HANDLE *handle;

    list = tv->vec + entry_index;
    std::list<mid_t>::iterator itr = list->begin();

    // 链表搬迁
    for (; itr != list->end(); )
    {
        handle = (struct TIMER_HANDLE *)memunit_get(*itr);
        if(handle==nullptr)
        {
            assert_noeffect(0);
        }
        else
        {
            // 节点搬迁
            // 节点搬迁时一般都是往低级的时间轮搬,如果时间间隔特别大的tick,有可能还会添在同一链表,所以添加定时器最好用头插法,
            int ret = _internal_add_timer(handle); 
            assert_retval(ret == 0, ret);
        }
        itr = list.earse(itr);
    }

    return entry_index;
}

单级时间轮实现

虽然简单,不变的是用时间间隔确定层级(rotation),用绝对时间确定在那条链表(那个槽位)上

#ifndef TIME_WHEEL_TIMER
#define TIME_WHEEL_TIMER

#include <time.h>
#include <netinet/in.h>
#include <stdio.h>

#define BUFFER_SIZE 64
class tw_timer;
struct client_data
{
    sockaddr_in address;
    int sockfd;
    char buf[ BUFFER_SIZE ];
    tw_timer* timer;
};


// 定时器类
class tw_timer
{
public:
    tw_timer( int rot, int ts ) 
    : next( NULL ), prev( NULL ), rotation( rot ), time_slot( ts ){}

public:
    int rotation; //记录定时器在时间轮转多少圈后生效
    int time_slot;//记录定时器属于时间轮上的哪个槽
    void (*cb_func)( client_data* ); //cb_func
    client_data* user_data;          //cb_data
    tw_timer* next;                  //便于删除,链表的节点
    tw_timer* prev;  
};

// 定时器容器类:单级时间轮
class time_wheel
{
public:
    time_wheel() : cur_slot( 0 )
    {
        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 ) // timeout是时间间隔
    {
        if( timeout < 0 )
        {
            return NULL;
        }
        int ticks = 0;
        if( timeout < TI )
        {
            ticks = 1;
        }
        else
        {
            ticks = timeout / TI;
        }
        int rotation = ticks / N;                  // 待插入的定时器 在 时间轮转动多少圈后触发
        int ts = ( cur_slot + ( ticks % N ) ) % N; // 应该被插入到哪个槽中  时间间隔+cur_slot取得类自然时间 判断放入那个槽位 
        // 时间轮并没有走,走的是cur_slot, 在不同的链表间步进
        tw_timer* timer = new tw_timer( rotation, ts );
        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;
        }
    }
    void tick()
    {
        tw_timer* tmp = slots[cur_slot];
        printf( "current slot is %d\n", cur_slot );
        while( tmp )
        {
            printf( "tick the timer once\n" );
            // 定时器的rotation>0 在这一轮不会起作用
            if( tmp->rotation > 0 )
            {
                tmp->rotation--; // 相当于1点1分,2点1分,3点1分 rotation是更高的层级
                tmp = tmp->next;
            }
            else // 
            {
                tmp->cb_func( tmp->user_data );

                // 执行完毕定时任务后删除定时器, 可以直接调用del
                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;
                }
            }
        }
        cur_slot = ++cur_slot % N; // 下次循环看哪个槽
    }

private:
    static const int N = 60;
    static const int TI = 1; 
    tw_timer* slots[N];
    int cur_slot;
};

#endif




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值