skynet初探

Actor

并发actor(数据+行为+消息)之间并不共享内存,不存在相互调用,只有消息通知。分布式的特点,不在意是在本地还是远程,很好的解决了数据共享的问题(自身维护自己的数据状态,只通过接受消息进行处理请求,内部串行处理消息)

在这里插入图片描述

skynet服务
概述

Skynet 只负责把一个数据包从一个服务内发送出去,让同一进程内的另一个服务收到,调用对应的callback 函数处理。它保证,模块的初始化过程,每个独立的 callback 调用,都是 相互线程安全 的。编写服务的人不需要特别的为多线程环境考虑任何问题,专心处理发送给它的一个个数据包

  • C语言 实现的消息循环和组件加载机制;
  • lua 实现的以消息为中心的进入退出 coroutine(协程)的包装层。
//初始化节点模块,用于集群,转发远程节点的消息
skynet_harbor_init(config->harbor);
//初始化句柄模块,用于给每个Skynet服务创建一个全局唯一的句柄值
skynet_handle_init(config->harbor);
//初始化消息队列模块,这是Skynet的主要数据结构
skynet_mq_init();
//初始化服务动态库加载模块,主要用于加载符合Skynet服务模块接口的动态链接库(.so)
skynet_module_init(config->module_path);
//初始化定时器模块
skynet_timer_init();
//初始化网络模块
skynet_socket_init();
//加载日志模块
skynet_profile_enable(config->profile);

服务与消息的关联
初始化一个服务:

  • 一个skynet_contex作为服务的实例
  • 一个唯一的服务handle,即服务的唯一id,用于识别
  • 一个消息队列message_queue(该服务的队列,即次级队列)
  • 向框架注册一个callback,当服务收到发来的消息,使用这个方法

handle是32位整数,生成时把该节点的harbor id写入handle的高8位 (因为只有8位,则skynet集群最多256个节点, 一个节点中最多24位个服务,1.6M个 (低24位用来分配给本节点的handle)

消息队列
在skynet中所有的消息都是异步的

  • ctx->queue:每个服务的私有消息队列
  • global_queue:全局消息队列
  • skynet_globalmq_push:二级消息队列压入全局消息队列的接口
  • skynet_context_message_dispatch:从消息队列中取出消息来执行

在服务被创建时有自己的消息队列,并且注册到全局的消息队列中 (严格上:全局存放的是私有消息队列不为空的Actor)

struct skynet_message{
  uint32_t source;   //服务实例handle
  int session;       //上下文的标识
  void* data;        //消息地址指针
  size_t sz;         //size
};
//
//全局队列
struct message_queue{
    struct spinlock lock;   //锁
    uint32_t handle;        //消息队列所属服务的句柄
    int cap;                //容量
    int head;               
    int tail;
    int release;            //队列是否被释放
    int in_global;          //是否存入全局队列标志
    struct skynet_message * queue; 
    struct message_queue *next;    //与其他消息队列的关联
}


//全局消息队列结构
struct global_queue {
    struct message_queue *head;
    struct message_queue *tail;
    //锁(自旋锁或互斥锁)
    struct spinlock lock;
};
//全局消息队列的静态结构指针
static struct global_queue *Q = NULL;

工作线程

static void *thread_worker(void *p) {
//初始化
struct worker_parm *wp = p;
int id = wp->id;
int weight = wp->weight;
struct monitor *m = wp->m;
struct skynet_monitor *sm = m->m[id];
skynet_initthread(THREAD_WORKER);
struct message_queue * q = NULL;
//循环调用 skynet_context_message_dispatch
while (!m->quit) {
    q = skynet_context_message_dispatch(sm, q, weight);
    //消息队列无消息可执行则挂起当前工作线程
    if (q == NULL) {
        if (pthread_mutex_lock(&m->mutex) == 0) {
            ++ m->sleep;
            // "spurious wakeup" is harmless,
            // because skynet_context_message_dispatch() can be call at any time.
            if (!m->quit)
                pthread_cond_wait(&m->cond, &m->mutex);
            -- m->sleep;
            if (pthread_mutex_unlock(&m->mutex)) {
                fprintf(stderr, "unlock mutex error");
                exit(1);
            }
        }
    }
}
return NULL;
}

通过while 不断的从skynet_context_message_dipatch 从全局消息对列中取出消息来执行,直到全部执行完,当前线程进入wait

  • 首先,通过 skynet_globalmq_pop 从全局消息队列中得到一个服务的私有消息队列 q;
  • 然后,通过 skynet_mq_pop 从 q 中取到一条具体的消息来执行;
  • 最后,通过 dispatch_message 执行了消息后,判断当前 q 队列是否已经没有其他消息:假如有则将 q 通过 skynet_globalmq_push 放回全局消息队列中;假如没有,则不再将 q 放回。
    注册回调函数
function skynet.start(start_func)
    -- 将dispatch_message 绑定为消息的回调函数
    c.callback(skynet.dispatch_message)
end

RPC

向外部服务发起一个远程调用,等待对方发送回应之后逻辑再继续执行下去,为了把这种回调函数的模式转为阻塞api,引入coroutine,运行一半时挂起,之后在适当的时候继续运行。。。。所以在接收到每条消息都会创建一个co 去执行该消息的dispatch

local command = {}
function command.foobar(...)
end

local function dispatch(cmd, ...)
    command[cmd](...) --第一种
    --第二种更好
    local f = assert(command[cmd])
    --执行 cmd 对应的处理函数
    local r = f(...)
    --判断是否有返回结果
    if r ~= NORET then
        --打包数据并回传给消息发送方
        skynet.ret(skynet.pack(r))
    end
end
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值