skynet学习笔记 源码之skynet_timer定时器

定时器简介

skynet.timeout(ti,func)

skynet定时器非常的轻量级,仅仅提供注册函数,并且还不能传参。

定时器的实现仅仅用了300行代码。

定时器注册回调流程

通过调用capi把定时任务加入,最终以消息的方式传回本actor,用worker线程处理消息,执行注册函数。

function skynet.timeout(ti, func)

    local session = c.intcommand("TIMEOUT",ti)

    ....

end

static int lintcommand(lua_State *L) {

result = skynet_command(context, cmd, parm);

...

}

cmd_timeout(struct skynet_context * context, const char * param) {

...

    skynet_timeout(context->handle, ti, session);

...

}

int

skynet_timeout(uint32_t handle, int time, int session) {

    if (time <= 0) {

        struct skynet_message message;

        message.source = 0;

        message.session = session;

        message.data = NULL;

        message.sz = (size_t)PTYPE_RESPONSE << MESSAGE_TYPE_SHIFT;



        if (skynet_context_push(handle, &message)) {

            return -1;

        }

    } else {

        struct timer_event event;

        event.handle = handle;

        event.session = session;

        timer_add(TI, &event, sizeof(event), time);

    }

    return session;

}

源码原理分析

skynet的设计思想参考Linux内核动态定时器的机制,参考Linux动态内核定时器介绍linux 内核定时器 timer_list详解 - 海王 - 博客园

近期链表:0-255个滴答数的定时任务会加入。特征:链表里都是触发时间相同的

散列链表:256 <= interval <= 0xffffffff 加入。   特征:链表里触发时间不一定相同

滴答数: skynet的定时器精度为0.01秒,按照它算法的定义就是0.01为1个滴答数。

到期时间:每一个加入的定时任务都会给它标记一个到期时间(加入时定时器当前滴答数+定时滴答数)。

skynet每次update都会取当前时间减去上次update时间获取流逝的滴答数。

然后把流逝的滴答数一滴答一滴答的执行。

执行过程中只关心到期间隔时间为 0 到 255 滴答数的处理。暂时不关注256 <= interval <= 0xffffffff。

滴答滴答流逝过程中,每走完一次0到255就会有一个对应的散列链表被移动(其实就是重新加入定时器,会重新给它们定位。

skynet.h

int skynet_timeout(uint32_t handle, int time, int session);      //注册定时任务

void skynet_updatetime(void);                                               //更新定时任务处理

void skynet_timer_init(void);                                                  //初始化

数据结构

#define TIME_NEAR_SHIFT 8

#define TIME_NEAR (1 << TIME_NEAR_SHIFT)

#define TIME_LEVEL_SHIFT 6

#define TIME_LEVEL (1 << TIME_LEVEL_SHIFT)

#define TIME_NEAR_MASK (TIME_NEAR-1)

#define TIME_LEVEL_MASK (TIME_LEVEL-1)



struct timer_event {

    uint32_t handle;

    int session;

};



struct timer_node {

    struct timer_node *next;

    uint32_t expire;

};



struct link_list {

    struct timer_node head;

    struct timer_node *tail;

};

struct timer {

    struct link_list near[TIME_NEAR];  //近期链表长度256 记录0到255间隔触发的定时任务

    struct link_list t[4][TIME_LEVEL];   //散列链表记录256 到 0xffffffff间隔触发的定时任务

    struct spinlock lock;                        //自旋锁

    uint32_t time;                                 //定时器自增的滴答数

    uint32_t starttime;                          //程序开始时间

    uint64_t current;                             //上次记录的系统时间

    uint64_t current_point;                   //上次记录的从开机到现在的时间,不受系统时间修改影响

};

定时器驱动函数

sknyet定时间隔0.00025秒调一次skynet_updatetime,

skynet_updatetime会获取当前运行时间,然后减去上次记录时间,拿到一个流逝的滴答数,然后调用timer_update

static void *

thread_timer(void *p) {

   ....

    for (;;) {

        skynet_updatetime();

        usleep(2500);

    }

.....

}



void

skynet_updatetime(void) {

    uint64_t cp = gettime();

    if(cp < TI->current_point) {

        skynet_error(NULL, "time diff error: change from %lld to %lld", cp, TI->current_point);

        TI->current_point = cp;

    } else if (cp != TI->current_point) {

        uint32_t diff = (uint32_t)(cp - TI->current_point);

        TI->current_point = cp;

      TI->current += diff;

        int i;

        for (i=0;i<diff;i++) {

            timer_update(TI);

        }

    }

}

static void 

timer_update(struct timer *T) {

    SPIN_LOCK(T);



    // try to dispatch timeout 0 (rare condition)

    timer_execute(T);                                               



    // shift time first, and then dispatch timer message

    timer_shift(T);



    timer_execute(T);



    SPIN_UNLOCK(T);

}

处理到期的定时任务

static inline void

timer_execute(struct timer *T) {

    int idx = T->time & TIME_NEAR_MASK;     取得当前处于0到255的哪个位置

    while (T->near[idx].head.next) {

        struct timer_node *current = link_clear(&T->near[idx]);  //取出链表数据

        SPIN_UNLOCK(T);

        // dispatch_list don't need lock T

        dispatch_list(current);                                                     //处理到期任务

        SPIN_LOCK(T);

    }

}

调整散列列表

static void

timer_shift(struct timer *T) {

    int mask = TIME_NEAR;

    uint32_t ct = ++T->time;       //自增滴答数

    if (ct == 0) {                          //add_node ct为0时会加入到 散列列表3,0位置

        move_list(T, 3, 0);

    } else {

        uint32_t time = ct >> TIME_NEAR_SHIFT;

        int i=0;

        while ((ct & (mask-1))==0) {                    前 7位都为0时满足条件,也就是每过255滴答数触发一次。

            int idx=time & TIME_LEVEL_MASK;  取得散列列表的位置id

            if (idx!=0) {

                move_list(T, i, idx);                          调整

                break;              

            }

            mask <<= TIME_LEVEL_SHIFT;

            time >>= TIME_LEVEL_SHIFT;

            ++i;

        }

    }

}

调整函数就是把列表内容拿出来重新加入到定时器。

static void

move_list(struct timer *T, int level, int idx) {

    struct timer_node *current = link_clear(&T->t[level][idx]);

    while (current) {

        struct timer_node *temp=current->next;

        add_node(T,current);

        current=temp;

    }

}

加入定时器处理

static void

add_node(struct timer *T,struct timer_node *node) {

    uint32_t time=node->expire;     //该定时任务触发滴答数

    uint32_t current_time=T->time; //定时器当前滴答数

   

    if ((time|TIME_NEAR_MASK)==(current_time|TIME_NEAR_MASK)) {  
前7位可以不一样,7位以后必须一样,也就是说间隔触发时间只有0-255之间(特殊情况,current_time为0的时候time不为0会加入到散列链表 3,0位置)

        link(&T->near[time&TIME_NEAR_MASK],node);

    } else {

        int i;

        uint32_t mask=TIME_NEAR << TIME_LEVEL_SHIFT;

        for (i=0;i<3;i++) {

            if ((time|(mask-1))==(current_time|(mask-1))) {

                break;

            }

            mask <<= TIME_LEVEL_SHIFT;

        }



        link(&T->t[i][((time>>(TIME_NEAR_SHIFT + i*TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);    

    }

}

仅个人理解,供参考

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值