本章的内容是
- 设置定时器
- 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版权协议,转载请附上原文出处链接和本声明。