这里介绍下skynet的线程。
4种线程
skynet一共有4种线程,monitor线程用于检测节点内的消息是否堵住,timer线程运行定时器,socket线程进行网络数据的收发,worker线程则负责对消息队列进行调度(worker线程的数量,可以通过配置表指定)。消息调度规则是,每条worker线程,每次从全局消息队列中pop出一个次级消息队列,并从次级消息队列中pop出一条消息,并找到该次级消息队列的所属服务,将消息传给该服务的callback函数,执行指定业务,当逻辑执行完毕时,再将次级消息队列push回全局消息队列中。因为每个服务只有一个次级消息队列,每当一条worker线程,从全局消息队列中pop出一个次级消息队列时,其他线程是拿不到同一个服务,并调用callback函数,因此不用担心一个服务同时在多条线程内消费不同的消息,一个服务执行,不存在并发,线程是安全的
socket线程、timer线程甚至是worker线程,都有可能会往指定服务的次级消息队列中push消息,push函数内有加一个自旋锁,避免同时多条线程同时向一个次级消息队列push消息的惨局。
skynet运行机制
skynet每个服务最重要的是设置一个callback函数,服务向另一个服务发消息都会压入这个服务的消息队列,等待_worker线程从全局队列取出一个服务的消息队列,然后再从这个消息队列取出一个消息使用这个服务的callback处理
游戏服务器一般会启动一个socket服务教给gate.so管理,socket服务可以接受外部msg传递到内部服务来处理。
1.main函数
main函数是skynet进程的入口点,它需要一个配置文件的路径作为参数。main函数先做一些内存分配工作,然后加载配置文件里的内容。再把配置文件里的内容设置到lua环境变量_ENV。然后从_ENV里读取配置到c配置中。最后调用skynet_start。
2.skynet_start函数
skynet_start函数根据配置确定服务是不是以后台方式启动,然后初始化timer/socket/module/mq这些数据结构,给它们分配内存,填充一些必要的字段。下一步创建和注册日志logger服务,这个logger服务是C层实现的。然后创建注册snax服务,做bootstrap操作。最后调用start函数。
3.start函数
这个函数会启动timer/socket/monitor/worker线程,timer/socket/monitor线程都只有一个,唯独worker线程是多个,在配置文件中以thread=num来配置的。如果配置文件中没有配,就是8个。第个worker线程都有自己的权重值,权重值越大的,给单个服务的时间片越小。
线程都启动完以后,就进入pthread_join,等待线程的终止。所有线程终止以后,skynet就会退出。
例如:
在网络游戏中,你可以为每个在线用户创建一个 lua 虚拟机(skynet 称之为 lua 服务),姑且把它称为 agent 。用户在不和其它用户交互而仅仅自娱自乐时,agent 完全可以满足要求。agent 在用户上线时,从数据库加载关联于它的所有数据到 lua vm 中,对用户的网络请求做出反应。当然你也可以让一个 lua 服务管理多个在线用户,每个用户是 lua 虚拟机内的一个对象。
你还可以用独立的服务处理网络游戏中的副本(或是战场),处理玩家和玩家间,玩家协同对战 AI 的战斗。agent 会和副本服务通过消息进行交互,而不必让用户客户端直接与副本通讯。
我们通常建议使用一个网关服务(gate),专门监听端口,接受新连接。在用户身份确定后,再把真正的业务数据转交给特定的服务来处理。同时,网关还会负责按约定好的协议,把 TCP 连接上的数据流切分成一个个的包,而不需要业务处理服务来分割 TCP 数据流。业务处理的服务不必直接面对 socket 句柄,而由 skynet 正常的内部消息驱动即可。
No te tomes la vida en serio, al fin y al cabo no saldrás vivo de ella.