先说说多核并发编程
可以分情况讨论
多线程
创建CPU核心数的线程数量,memcached就是使用多线程。 多个线程在同一个进程中,涉及到很多临界资源的访问,所以要考虑到加锁。 如下图。 
多进程
和多线程类似,都是创建CPU核心数量个进程。Nginx就是使用多进程的。 Nginx通过共享内存来进行共享数据,也需要考虑使用锁。 如下图。 
CSP
以go语言作为代表,并发实体是协程(用户态线程、轻量级线程),内部也可以使用多个核心开启内核线程去充分使用多核。 可以借鉴我的前面博客写的协程框架。
Actor
这个也是skynet最主要的使用多核并发模型。
erlang语言层面支持actor并发模型,并发实体是actor(在skynet中称为服务); 在skynet中就是采用C+lua来实现actor并发模型,底层通过采用核心绑定内核线程来充分利用多核。
先说说actor模型是如何
并发编程的几个概念
- 保证数据的一致性、隔离性、正确性和并发性
- 通信策略有两种:共享数据、消息传递
- 粒度
actor出现是解决什么问题
使用共享数据的并发编程面临最大的问题就是数据条件的竞争,需要处理各种锁和死锁的问题。因此可以使用消息传递来减弱这种数据竞争的状态。 其次就是随着项目增大,业务代码越来越多,不可避免的大量使用低效的锁,并没有规范的额编程模型,是最后肯定也会出现问题,最根本的原因就是没有将系统的任务调度抽象出来,由于任务调度和业务逻辑耦合在一起了,很难高度的抽象保证任务调度的有序。
actor特点(后面根据这些特点讲述skynet的actor是如何实现的)
优点
缺点
- 弱一致性
- 很难保证各个actor之间粒度,会出现单个actor出现阻塞的情况
- actor之间的通信慢
在skynet中的actor(服务)的特点
- 并行执行、强隔离性
- 单个actor是一个服务,每个服务都是独立的一个抽象进程
- 基于消息进行计算,actor通过消息进行共享数据
skynet的组成
大体的运行过程: 用户态中含有多个抽象进程(隔离的环境),一个fd对应一个进程,从消息队列中取出一个消息,消息作为回调函数的参数运行,每个消息都是在抽象进程中独立运行。
隔离的环境
是指一个独立的lua虚拟机。
消息队列
消息分类:
- 网络的消息:客户端的消息
- actor之间的消息
- 定时线程的消息
消息队列有全局的消息队列和actor的消息队列。全局的消息队列重要用来接收网络中的待处理的全部消息类型。actor中的消息队列用来储存属于自己的消息,当actor中有消息时就说明这个actor是活跃的,需要被调度的。
回调函数
当actor是活跃的,回调函数会从actor中消息队列中取出消息,并作为回调函数的参数运行actor。一个actor就是一个lua虚拟机,代表一个抽象的进程。
网络IO的管理
skynet同样使用epoll来对网络IO进行管理,skynet是基于reactor的网络模型。 
整个框架的模型

整个框架如何运行?
采用生产者和消费者模型
产品
生产者:
- 网络线程
- actor工作线程
- 定时器线程
消费者 - actor工作线程
调度流程
核心:线程池 生产
- 网络线程收集出网络事件,将事件包装成消息发送到对应的actor的消息队列中
- actor在执行中可能会使用到一些延时任务,将任务交由定时线程,当一段时间过后,定时线程又会将消息返回给actor的消息队列当中
消费
- 线程池的工作线程去检查活跃的actor(消息队列当中有消息),消息队列是并发执行的
- 分权重,前4个线程会先执行一个消息,后面4个线程会执行完所有消息 ,错位执行:优化,避免某个actor堆积大量消息。
工作线程执行流程
工作线程从全局队列中 pop 出单个 Actor 消息队列;从 Actor 消息队列中按照规则 pop 出一定数量的消息进行执行;若 Actor 消息队列中仍有消息继续放入全局队列队尾;若 Actor 消息队列中没有消息则不放入全局队列中;全局队列只存活跃的 Actor 消息队列;
工作线程会按照各自的权重按照不同的执行规则分发消息。
总结
skynet大致的工作过程和原理大概如此,后续会翻看源码再总结。
学习来源:零声学院 免费公开课程,个人觉得老师讲得不错,分享给大家Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容
|