介绍
常见解决并发的策略一般有两种:共享数据和消息传递
基于消息传递的实现有
- CSP模型,典型的有Go语言中基于Channel的通讯
- Actor模型,典型的有Akka中的Actor模型
CSP模型和Actor模型的简单理解:
Don't communicate by sharing memory; share memory by communicating. (R. Pike)
不要通过共享内存来通信,而应该通过通信来共享内存
所以常见的并发模型一般有3中,基于线程与锁的内存共享模型、Actor模型和CSP模型
基于线程与锁的内存共享模型
这种并发模型有悲观锁的意思,每次多线程并发访问内存需要先获取锁,然后才能设置内存,常用的编程语言都支持通过锁来保证并发安全
Actor模型
Actor 的基础就是消息传递,一个 Actor 可以认为是一个基本的计算单元,它能接收消息并基于其执行运算,它也可以发送消息给其他 Actor。Actors 之间相互隔离,它们之间并不共享内存。
Actor 本身封装了状态和行为,在进行并发编程时,Actor 只需要关注消息和它本身。而消息是一个不可变对象,所以 Actor 不需要去关注锁和内存原子性等一系列多线程常见的问题。
所以 Actor 是由状态(State)、行为(Behavior)和邮箱(MailBox,可以认为是一个消息队列)三部分组成:
- 状态:Actor 中的状态指 Actor 对象的变量信息,状态由 Actor 自己管理,避免了并发环境下的锁和内存原子性等问题。
- 行为:Actor 中的计算逻辑,通过 Actor 接收到的消息来改变 Actor 的状态。
- 邮箱:邮箱是 Actor 和 Actor 之间的通信桥梁,邮箱内部通过 FIFO(先入先出)消息队列来存储发送方 Actor 消息,接受方 Actor 从邮箱队列中获取消息。
Actor也有监督节点(父级节点),一个监督者(父级节点)可以决定在某些类型的失败时重新启动其子 Actor,或者在其他失败时完全停止它们。
Akka就是使用Actor模型进行高性能通讯
CSP模型
CSP的是Communicating Sequential Processes(CSP)的缩写,翻译成中文是顺序通信进程
典型实现就是Go语言中的Channel设计,Channel用于多个Goroutine(轻量级协程)之间通讯的,本身设计是并发安全的
示例:
要找出10000以内所有的素数,这里使用的方法是筛法,即从2开始每找到一个素数就标记所有能被该素数整除的所有数。直到没有可标记的数,剩下的就都是素数。下面以找出10以内所有素数为例,借用 CSP 方式解决这个问题。
从上图中可以看出,每一行过滤使用独立的并发处理程序,上下相邻的并发处理程序传递数据实现通信。通过4个并发处理程序得出10以内的素数表,对应的 Go 实现代码如下:
代码:
func main() {
origin,wait := make(chan int),make(chan struct{})
Processor(origin,wait)
for num := 2 ; num < 10000; num++ {
origin <- num
}
close(origin)
<- wait
}
func Processor(seq chan int, wait chan struct{}) {
go func() {
prime,ok := <- seq
if !ok {
close(wait)
return
}
fmt.Println(prime)
out := make(chan int)
Processor(out,wait)
for num := range seq {
if num % prime != 0 {
out <- num
}
}
close(out)
}()
}