有人在群里问到为什么skynet.start函数的参数为要在一个定时器中执行,不能直接执行吗?skynet.start函数如下:
function skynet.start(start_func)
c.callback(skynet.dispatch_message)
skynet.timeout(0, function()
skynet.init_service(start_func)
end)
end
有人认为这是为了一个服务尽快返回。他们认为启动一个新的服务时,由于这个操作是阻塞的,要尽快让这个操作返回,因为b服务的文件里可能有耗时的计算。
其实这个观点是错误的,a在启动另一个服务b,还没有到加载b服务的文件时,其实b服务就已经返回了,产生了一个服务地址,此时a服务就可以向b服务发送消息了。加载b服务的文件其实也有消息驱动的,下面是一个服务启动另一个服务时的简要时序图:
原因之一是在skynet.timeout中有一个创建协程的操作,要保证所有函数执行的都是在协程里进行的。为什么呢?要找到问题的原因,一定要看看源码的实现,skynet.timeou的实现如下:
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底层skynet.timeout会发送一个定时为0的定时器消息,然后在创建一个协程。我们来探讨一下为什么要有这两步。
首先一些阻塞的调用如skynet.call是利用协程实现的,如果skynet.start的执行函数不在协程里执行,那么调用skynet.call将会报错--attempt to yield from outside a coroutine。由于调用skynet.call的语义就是将当前执行的函数挂起,这样一来就只能利用协程来实现了,因为你是不能把协程之外的函数挂起的。如果阻塞call都没有实现,那一定不是好的实现。
还有就是skynet.fork的实现。如果skynet.start主函数不在协程里执行,那么skynet.fork将无处施展。
说了为什么要创建协程,我还是没有讲清楚为什么要开启一个定时器消息。我思来想去,我的理由是为了方便统一处理,既然后续的逻辑执行都是一个个消息回调函数的处理,而且一开始主函数就要处理协程的问题,那么干脆这个主函数也用回调消息的方式来执行。
欢迎加入QQ群 858791125 讨论skynet,游戏后台开发,lua脚本语言等问题。