【手游项目5】skynet-1


skynet 概述
Skynet 的核心功能就是发送消息和处理消息
充分利用多核优势,将不同的业务放在独立的执行环境中处理,协同工作,Lua State 已经提供了良好的沙盒,隔离不同执行环境;
多线程模式,可以使得状态共享、数据交换更加高效。

https://blog.codingnow.com/2012/09/the_design_of_skynet.html
https://blog.csdn.net/SnailCpp/article/details/80721756
1.
为了提供高效的服务间通讯,Skynet 采用了几点设计,获得了比多进程方案更高的性能。
数据包通常是在一个服务内打包生成的,Skynet 并不关心数据包是怎样被打包的,
它甚至不要求这个数据包内的数据是连续的(虽然这样很危险,在后面会谈及的跨机通讯中会出错,
除非你保证你的数据包绝对不被传递出当前所在的进程)。它仅仅是把数据包的指针,
以及你声称的数据包长度(并不一定是真实长度)传递出去。由于服务都是在同一个进程内,接收方取得这个指针后,
就可以直接处理其引用的数据了。
这个机制可以在必要时,保证绝对的零拷贝,几乎等价于在同一线程内做一次函数调用的开销。
但,这只是 Skynet 提供的性能上的可能性。它推荐的是一种更可靠,性能略低的方案:
它约定,每个服务发送出去的包都是复制到用 malloc 分配出来的连续内存。接收方在处理完这个数据块(在处理的 callback 函数调用完毕)后,会默认调用 free 函数释放掉所占的内存。即,发送方申请内存,接收方释放。

跨服务的调用也有其缺点,那就是一旦你做了一次跨服务的远程调用,即使没有性能上太多的损失,
也会遇到对服务内状态改变不可预知的烦恼。
就是说,你发起一个远程请求后,下一个收到的消息包,不一定是对这次请求的回应。
它可能是其它服务对你的请求,这个请求很可能改变你自己的内存状态。
(skynet_command )

提问:意外情况发送时出去的包没有被接收,会内存泄漏吗?

 
2.
skynet 的消息调度
Skynet 维护了两级消息队列。
每个服务实体有一个私有的消息队列,队列中是一个个发送给它的消息。消息由四部分构成:
struct skynet_message {
    uint32_t source;
    int session;
    void * data;
    size_t sz;
};
向一个服务发送一个消息,就是把这样一个消息体压入这个服务的私有消息队列中。这个结构的值复制进消息队列的,但消息内容本身不做复制。
Skynet 维护了一个全局消息队列,里面放的是诸个不为空的次级消息队列。
在 Skynet 启动时,建立了若干工作线程(数量可配置),它们不断的从主消息列队中取出一个次级消息队列来,再从次级队列中取去一条消息,调用对应的服务的 callback 函数进行出来。为了调用公平,一次仅处理一条消息,而不是耗净所有消息(虽然那样的局部效率更高,因为减少了查询服务实体的次数,以及主消息队列进出的次数),这样可以保证没有服务会被饿死。
用户定义的 callback 函数不必保证线程安全,因为在 callback 函数被调用的过程中,其它工作线程没有可能获得这个 callback 函数所熟服务的次级消息队列,也就不可能被并发了。一旦一个服务的消息队列暂时为空,它的消息队列就不再被放回全局消息队列了。这样使大部分不工作的服务不会空转 CPU 。
btw,在做这部分代码实现时,我曾经遇到过一些并发引起的 bug ,好在最终都解决了。这或许是整个系统中,并发问题最复杂的部分,但也仅仅是这一小部分了。不会让并发的复杂性蔓延出去。
 
用户定义的 callback 函数不必保证线程安全,因为在 callback 函数被调用的过程中,
其它工作线程没有可能获得这个 callback 函数所属服务的次级消息队列,也就不可能被并发了。

因为每个服务只有一个次级消息队列,每当一条worker线程,从全局消息队列中pop出一个次级消息队列时,其他线程是拿不到
同一个服务,并调用callback函数,因此不用担心一个服务同时在多条线程内消费不同的消息,一个服务执行,不存在并发,线程是安全的

提问:由于服务器内部是消息驱动,可以理解服务内部的消息处理是单线程?
 
3.build
https://github.com/cloudwu/skynet/wiki/Build
https://blog.csdn.net/chenxijie1985/article/details/88996714
https://blog.csdn.net/duguduchong/article/details/8699774
https://www.cnblogs.com/gyfluck/p/10523832.html

4.codecache.clear()
注意,codecache.clear() 仅仅只是创建一个新的 cache ( api 名字容易引起误会),而不释放内存。所以不要频繁调用。如果你需要你加载文件不受 cache 影响,正确的方式是自己读出代码文本,并用 loadstring 方式加载;而不是在加载前调用 codecache.clear 。 

5.skynet.queue
同一个 skynet 服务中的一条消息处理中,如果调用了一个阻塞 API ,那么它会被挂起。挂起过程中,这个服务可以响应其它消息。这很可能造成时序问题,要非常小心处理。
换句话说,一旦你的消息处理过程有外部请求,那么先到的消息未必比后到的消息先处理完。且每个阻塞调用之后,服务的内部状态都未必和调用前的一致(因为别的消息处理过程可能改变状态)。
skynet.queue 模块可以帮助你回避这些伪并发引起的复杂性。


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值