之前对skynet的印象,主要是来自于我对golang的理解,对gevent开发的经验,以及云风的blog。对于底层的代码,并没有仔细去阅读过。最近在实现业务系统的时候,发现有同事在同一个函数里做了一个互斥的判断,才发现对skynet的理解有误。
以前,用python的gevent框架实现游戏服务器的时候,会针对每个玩家建立3-5个greenlet(协程),用于处理玩家身上的定时器事件,以及IO操作。然后还有少数几个协程处理全局的定时器事件。当然,战斗是放在独立的进程中实现的,那边基本上每个怪都有1-2个greenlet,一个房间里有几十个greenlet是很正常的。总结起来,就是python+gevent为了规避全局锁,采用了多进程,每个进程多greenlet的实现方式。
而对于golang,会变成底层是多线程模型,上面跑着多个goroutine,当一个goroutine发起阻塞式IO的时候,底层负责跑这个goroutine的线程,可以随之阻塞等待。系统便随即开一个新的线程,从一堆goroutine里挑一个可以执行的,继续执行。golang就相当于用多线程,取代了以前py的多进程。
读完云风的blog,我对skynet的理解,就是每个线程来跑一个Lua的VM,一系列的VM组成了一个队列。然后每个VM有自己的消息队列,VM运行时,就从自己的消息队列里,拿出一条消息执行。得益于Lua的沙盒机制,即使某个VM发生traceback,也能得到有效隔离。同时,因为同处一个进程内,VM之间数据交互可以做到相当高效,字符串之类的传递只要传指针即可。
但是,我之前忽略了Lua协程在其中发挥的作用。原来,一个服务(即一个VM)收到一个包req,就会新建一个协程进行处理。处理过程中,如果产生一个对外的call请求,req包是当成处理完的,当前的协程是会刮起,等待call请求返回来唤醒。这时候服务可以继续建立新的协程来处理新的消息包,而不会阻塞住。也就是说,如果处理一个包的过程中,发生了skynet.call调用,是会造成多个协程并发执行的。如果不注意用锁保护协程间的共享资源,就有可能出现问题。