传统的并发模型主要由两种实现的形式,一是同一个进程下,多个线程天然的共享内存,由程序对读写做同步控制(有锁或无锁). 二是多个进程通过进程间通讯或者内存映射实现数据的同步.

Actors模型

Actors模型更多的是用消息机制来实现并发,目标是让开发者不再考虑线程这种东西,每个Actor最多同时只能进行一样工作,Actor内部可以有自己的变量和数据。

在Actors模型中,每个Actor都有一个专属的命名”MailBox”, 其他Actor可以随时选择一个Actor通过邮箱收发数据,对于“MailBox”的维护,通常是使用发布订阅的机制实现的,比如我们可以定义发布者是自己,订阅者可以是某个Socket接口,另外的消息总线或者直接是目标Actor。

Actors模型避免了由操作系统进行任务调度的问题,在操作系统进程之上,多个Actor可能运行在同一个进程(或线程)中.这就节省了大量的Context切换。

CSP模型

CSP(Communicating Sequential Process)模型提供一种多个进程公用的“管道(channel)”, 这个channel中存放的是一个个”任务”. CSP中channel是第一类对象(first-class),它不关注发送消息的实体,而关注与发送消息时使用的channel。

原始的CSP中channel里的任务都是立即执行的,因为默认情况下的channel是无缓存的, 对channel的send动作是同步阻塞的,直到另外一个持有该channel引用的执行块取出消息(channel为空).反之,receive动作亦然。藉此,我们可以得到一个基本确定的事实,by default时,实际的receive操作只会在send之后才被发生。

除此以外,channel还有种Buffered Channel的模式,在默认情况的基础上,你可以确定channel内的消息数量,当channel中消息数量不满足于初始化时Buffer数目时,send动作不会被阻塞,写入操作会立即完成(因此Buffered Channel在很大程度上与Actor非常接近),直到Buffer数目已满,则send动作开始阻塞.go语言的channel就实现了有缓存和无缓存两种。

CSP与Actors有几个重要的区别

  • 在Actor的设计中,Actor与信箱是耦合的,而在CSP中channel是作为first-class独立存在的。
  • Actor中有明确的send/receive的关系,而channel中并不区分这样的关系,执行块可以任意选择发送或者取出消息。
  • CSP进程通常是同步的(即任务被推送进Channel就立即执行,如果任务执行的线程正忙,则发送者就暂时无法推送新任务),Actor进程通常是异步的(消息传递给Actor后并不一定马上执行).
  • CSP中的Channel通常是匿名的, 即任务放进Channel之后你并不需要知道是哪个Channel在执行任务,而Actor是有“身份”的,你可以明确的知道哪个Actor在执行任务.
  • 在CSP中,我们只能通过Channel在任务间传递消息, 在Actor中我们可以直接从一个Actor往另一个Actor传输数据.

想深入了解可以阅读并发之痛 Thread,Goroutine,Actor

题外话:Rust对并发的解决方案

Rust解决并发问题的思路是首先承认现实世界的资源总是有限的,想彻底避免资源共享是很难的,不试图完全避免资源共享,它认为并发的问题不在于资源共享,而在于错误的使用资源共享。

  • 定义类型的时候要明确指定该类型是否是并发安全的
  • 引入了变量的所有权(Ownership)概念 非并发安全的数据结构在多个线程间转移,也不一定就会导致问题,导致问题的是多个线程同时操作,也就是说是因为这个变量的所有权不明确导致的。有了所有权的概念后,变量只能由拥有所有权的作用域代码操作,而变量传递会导致所有权变更,从语言层面限制了竞态条件出现的情况。