一、并发和并行
这里首先要把并发(concurrency)和并行(parallellism)区分一下。并行一般只多个CPU或者多台计算机(分布式)同时运行程序,而并发一般是指在一台机器或者一颗CPU(可以多核心)来通过时间片模拟同时执行执行多个程序(但是从严格意义上来讲,如果多个程序被分配到不同的核心同时执行也是并行)。本篇只分析并发模型。
并发的模型分为两大类:一类是多线程并发;另外一类是多进程并发。而目前主流的基本都是多线程并发,当然,协程也跟了上来,但这现在不是重点。
而目前比较通用的两大类:Actor模型和CSP(Communicating Sequential Process)模型。前者在 Erlang、Scala中相当普遍,后者则在Golang中应用。
二、Actor模型
Actor模型可以简单理解成每一个Actor都有一个邮箱,可以收发很多邮件,但同时只能处理一封邮件。可以认为每个Actor都有一个处理缓冲区,它不管你外面的数据如何运作,他只对自己的邮箱负责。它的比较典型的应用是用Erlang语言写的OTP框架和Scala语言写的Akka分布式并发框架。
如果想完成一个优秀的Actor模型,需要处理的几个方面:
1、数量较多时如何保证Actor对象间通信的负载均衡。
2、分布式条件下的动态伸缩。
3、良好的异常控制和错误管理。
它其实可以说是一个分布式框架的要求。
Actor的另外一个优势在于,由于它只能处理一任务消息,所以它没有锁来控制状态,这就极大的减轻了在这方面的开发者的负担。Actor是异步实现的,但是它的消息处理过程是按照同步的机制实现的,这可以极大的减少线程之间的通信压力。
三、CSP模型
而在Golang语言中,一般使用一个公用的Channel(管道)来缓冲任务。管道可以分为阻塞和非阻塞,也可以设置管道容纳的数量大小。从而实现不同的任务需求。因为在Go语言中没有线程这个概念而是使用协程(Goroutine)这个机制,所以一时间可能没学过的会有些生疏,不过就把它当成用户态的线程即可。
在CSP模型中,其实就是匿名的进程通过通道来交换数据。通道的性质基本决定了整个数据交换的性质。
CSP模型对协程的处理,仍然需要细心的控制数据的交互,因为它无法保证活/死锁的消除,这就需要开发者自己了。
四、两者的应用场景
至于说这两种哪个好,还是老回答,实际情况决定。CSP支持同步,那么对于一些要求响应快的程序就可以考虑使用;Actor一般是异步实现,所以对于高并发的任务就可以考虑。而同步也意味着可能会阻塞,那么对于不希望阻塞的就不能考虑了。Actor的优势在于可以多个Actor之间互相通信,换句话说,是可以明确通信的双方身份的,而CSP则无法实现,除非在业务层增加判断标识。其实也就是说,Actor间的通信是自由的,而CSP只能借助于管道来实现,否则就无法进行通信。
从设计架构的耦合性来讲,通道当然更松散而邮箱显得更紧一些。当然这其中的一些状态转换也需要Actor来控制。
在实际的应用中,一般来说,CSP中的通道可能会被多个应用端应用。同时由于通道的受控性,相比于Actor的邮箱,可能CSP模型对于整个流控上控制的更好,这个意味着什么,做为一个有经验的服务端开发者来说不言而喻。相反,Actor却在这方面需要自己来控制,显得有点儿棘手。
两者既有各自的优势和劣势,可不可以把它们两者从某个层次上融合到一起呢?答案是肯定的。在Github上有一个开源的分布式的Actor模型,用Go实现的,有兴趣可以分析分析:
https://github.com/asynkron/protoactor-go
五、总结
Actor和CSP各有千秋也能够互相一起从某种层次上搞到一起,但是仍然是那句老话“实事求是”,以实际的应用场景为准。没有最好,只有最合适。既不能求全责备某一个模型不能全覆盖,更不苛求一个模型无所不能。能在实际场景中把某一个模型用好,让客户满意,让程序更容易编写、维护,这就是一个优秀的程序员!