skynet 学习笔记 - 定时器

本章的内容是

  • 设置定时器
  • skynet.timeout
  • 定时器的实现
  • 伪取消定时器

设置定时器

在业务Lua代码里设置定时器的接口是

    -- 参数 ti: number
    -- 参数 func: function
    -- 框架在 ti 个单位时间后,调用 func 这个函数。
    skynet.timeout(ti, func)

定时器实现的非常高效,一般不用太担心性能问题。

如果你的服务想大量使用定时器的话,可以考虑:在一个服务里,只使用一个 skynet.timeout,用它来触发自己的定时事件模块。

skynet.timeout

skynet.timeout 实现

function skynet.timeout(ti, func)
    local session = c.intcommand("TIMEOUT",ti)
    assert(session)
    local co = co_create(func)
    assert(session_id_coroutine[session] == nil)
    session_id_coroutine[session] = co
end
  • c.intcommand(“TIMEOUT”,ti) 在框架里注册了一个定时事件。事件关联了服务的唯一session
  • 分配一个协程。关联session和协程。
  • 在事件到期后,可以根据session, 找到这个协程,执行回调函数 func

再看看 c.intcommand(“TIMEOUT”,ti) 的实现

  • c.intcommand 就是 lua-skynet.c@lintcommand
  • lintcommand 调用 skynet_command(context, “TIMEOUT”, parm)
  • skynet_command 调用 cmd_timeout

cmd_timeout 实现

static const char *
cmd_timeout(struct skynet_context * context, const char * param) {
    char * session_ptr = NULL;
    int ti = strtol(param, &session_ptr, 10);
    int session = skynet_context_newsession(context);
    skynet_timeout(context->handle, ti, session);
    sprintf(context->result, "%d", session);
    return context->result;
}
  • 向框架申请,分配了这个服务唯一的session
  • skynet_timeout 注册了定时事件。 定时事件里记录了服务的handler, session

skynet 启动后,有1个线程执行

create_thread(&pid[1], thread_timer, m);

忽略很多细节,这个函数主要是不停地调用

    for (;;) {
        skynet_updatetime();
        // ...
    }

忽略细节,随着时间变化,对应的定时器到期,会触发

static inline void
dispatch_list(struct timer_node *current) {
    do {
        struct timer_event * event = (struct timer_event *)(current+1); //取出event,然后对skynet消息赋值
        struct skynet_message message;
        message.source = 0;
        message.session = event->session;
        message.data = NULL;
        message.sz = (size_t)PTYPE_RESPONSE << MESSAGE_TYPE_SHIFT;

        skynet_context_push(event->handle, &message);   //将消息压入相应的服务

        struct timer_node * temp = current;
        current=current->next;
        skynet_free(temp);  //处理完之后才释放内存
    } while (current);
}

dispatch_list 根据定时事件event 关联的source, session, 发送一个消息给对应的服务。

那么,服务收到后,就可以根据session 找到之前分配的协程了。

定时器的实现

定时器实现方案,从原理来说,非常简单。 伪代码如下

def update_timer():
     current = get_cur_time()
     for timer in reg_timer_list:
         if timer.expeires > current:
             timer.callback()

由时钟来驱动 update_timer。检查到期的定时事件,进行回调。

云大在skynet 里采用了时间轮的实现。按8/6/6/6/6/分成5个部分,也就是有5个时钟,这种分层方法的空间复杂度变为 256+64+64+64+64= 512个槽,支持注册最长时间的tick是 256*64*64*64*64=2^32。每个ticket 时长是 1/100秒

个人觉得:针对这个精度,时间轮太复杂了,采用更简单的实现,例如优先级队列。

更多关于定时器,可以看我之前的文章: 各种定时器的实现

伪取消定时器

skynet 在机制上不支持取消之前注册的定时器。

当倒计时结束以后,执行的不是调用定时器时注册的回调,而是更改了的回调。
如果这个回调不会做任何事情,这就是伪取消。

实现代码

local function remove_timeout_cb(...)
end

function skynet.remove_timeout(session)
    local co = co_create(remove_timeout_cb)
    assert(session_id_coroutine[session] ~= nil)
    session_id_coroutine[session] = co
end

版权声明:本文为samuelyao314原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。