Communicating sequential processes CSP 通信顺序进程 CSP writing to a file by name (process, Erlang) vs. writing to a file descriptor (channel, Go)
只通过消息传递通信来相互交
https://zh.wikipedia.org/wiki/通信顺序进程
一个巧克力售货机和它与一个想要买巧克力的人之间交互的抽象表示
CSP(通信顺序进程)_百度百科 https://baike.baidu.com/item/CSP/9076083
七周七并发模式:通信顺序进程(CSP)
概念
CSP
与actor模型类似,通信顺序进程(Communicating Sequential Processe,CSP)模型也是由独立的、并发执行的实体所组成,实体之间也是通过发送消息进行通信。但两种模型的重要差别是:CSP模型不关注发送消息的实体,而是关注发送消息时使用的channel(通道)。channel是第一类对象,它不像进程那样与信箱是紧耦合的,而是可以单独创建和读写,并在进程之间传递。
- 从这段话可以看出CSP本质上也是独立运行的执行单元,但是它没有mailbox,那么它执行的数据来自哪里呢?来自于channel。
Channel
一个channel就是一个线程安全的队列,任何任务只要持有channel的引用,就可以向一端添加消息,也可以从另一端删除消息。在actor模型中,消息是从指定的一个actor发往指定的另一个actor的;与之不同,使用channel发送消息时发送者并不知道谁是接收者,反之亦然。
- 也就是说CSP更像是在不同的actor之间共享mailbox,但是这里mailbox编程了channel。
默认情况下,channel是同步的(或称无缓存的)——一个任务向channel写入消息的操作会一直阻塞,直到另一个任务从channel中读出消息。
- emmm,阻塞了一个写入的线程。这样有些浪费线程,因此有了带
缓存区
的channel。当channel的缓存区有足够空间时,向其中写入消息的操作会立刻完成,不会阻塞。 - 对于有缓存的channel,一次性写入超过缓存区大小的数据时策略也是不同的,可以是只保留前面写入的(dropping),也可以是保留后面写入的(slide),甚至是直接阻塞式的(blocking),这个一般都有语言级别的策略支持。
- PS:是否支持可扩展的buffer这个问题书中提到是不支持,不过可能也只是这个语言不支持。因为我还没有看过其它的编程语言中是如何处理buffer的。
- channel也可以被关闭,此时read/write的处理不同的语言可能会有不同的处理方式。
go块
- 在说这个概念之前需要先看看在它之前的线程操作过程中遇到的问题,只有弄清楚了存在什么问题,我们才能理解为何会有go块,而它又是干什么的。
线程启动和运行时都有一定开销,这正是现在的程序都避免直接创建线程、转而使用线程池的原因。然而线程池并不总是适用。尤其是当程序阻塞时,使用线程池可能会造成麻烦。
线程池技术是处理CPU密集型任务的利器——任务进行时会占用某个线程,任务结束后将线程返还给线程池,使线程可以被复用。但涉及线程通信时使用线程池是否仍然合适呢?如果线程被阻塞,那么它将无限期被占用,这就削弱了使用线程池技术的优势。
这种问题是有一些解决方案的,但它们通常会对代码风格加以限制,使之变成事件驱动的形式。虽然这些方案都能解决问题,但它们破坏了控制流的自然的表达形式,让代码变得难以阅读和理解。更糟糕的是,这些方案还会大量使用全局状态,因为事件处理器需要保存一些数据,以便之后的事件处理器使用。
- 从上面的摘抄中已经可以看出来多线程、线程池其实是各种局限,而加入了事件驱动后也是有问题的。在这样的情况下,一个能够支持事件驱动,又可以保证代码在编写和阅读时看上去是顺序执行(这样比较符合人的阅读习惯和理解),同时还把状态数据封装起来的东西,emmm,此时我第一个反应就是unity中的Coroutine,而unity的coroutine本质是个状态机。这样一路的推导下来,我们也就明白了go块的本质就是一个状态机。而它的诞生也是为了解决上面说提到的种种问题。
- 本书中对于go块的使用demo使用了Clojure这个语言,我觉得没必要细说具体实现,因为每个语言也不同。不过go块中需要强调一点,它是非阻塞的。当然如果是阻塞的并不是不能工作,但是又回到了线程大量使用而又阻塞的问题了。
- 在书中提到go块的成本很低,这与线程不同,因为目前我所知道的语言中go块这样的概念都是基于协程的。golang中甚至没有线程的概念。而C#中task也是协程的。
使用CSP
- 实际上本章第二节的例子基本都是利用channel的同步性。比如超时处理就可以让一个定时器过一定时间后写入channel,在这之前另一个方法用来读取channel。
- 还有一些异步操作也是利用了channel的同步性,在以前可能异步操作需要使用回调,但是这样的话不同函数就成了互相调用的情况,但是使用CSP可以让函数基于channel来执行。比如:A函数读取一个channel的数据,在channel被写入数据前,调用A的线程阻塞;在IO操作完成后将结果写入channel,这样A函数就可以读取IO的结果了,而且两个函数不需要知道对方的存在,完全依赖channel来通信。
总结
- 首先与Actor比,CSP更加灵活。Actor负责通信的媒介(mailbox)与执行单元是紧耦合的,而CSP中channel可以被独立的创建、读写数据。从耦合性上将CSP更好。
- 然后CSP的go块使得异步编程更加高效,比起写很长的回调式的代码,CSP的代码更简洁。
- 至于书中提到的CSP的缺点:分布式和容错性支持的不够好,这个我个人觉得因语言而异,因为很少做相关的开发不好发表意见。
the-way-to-go_ZH_CN/01.2.md at master · Unknwon/the-way-to-go_ZH_CN https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/01.2.md
在声明和包的设计方面,Go 语言受到 Pascal、Modula 和 Oberon 系语言的影响;在并发原理的设计上,Go 语言从同样受到 Tony Hoare 的 CSP(通信序列进程 Communicating Squential Processes)理论影响的 Limbo 和 Newsqueak 的实践中借鉴了一些经验,并使用了和 Erlang 类似的机制。
Communicating sequential processes - Wikipedia https://en.wikipedia.org/wiki/Communicating_sequential_processes
communicating sequential processes
CSP 通信顺序进程
C.A.R.Hoare 1979
CSP是一种用来描述并行系统交互模式的形式语言,最早由C.A.R.Hoare(1934-)在其1979年的一篇论文中提出。
CSP对并发编程语言的设计有深远影响,受其影响的编程语言包括Limbo和Go等。
https://www.cs.cmu.edu/~crary/819-f09/Hoare78.pdf
https://xie.infoq.cn/article/a0880b7d215f7b82bc3a0380a
https://github.com/lintide/GoConcurrencyPatterns
Distinction
Go is the latest on the Newsqueak-Alef-Limbo branch, distinguished by first-class channels.
Erlang is closer to the original CSP, where you communicate to a process by name rather than over a channel.
The models are equivalent but express things differently.
Rough analogy: writing to a file by name (process, Erlang) vs. writing to a file descriptor (channel, Go).
1.1 Go语言创世纪 · Go语言高级编程 https://chai2010.cn/advanced-go-programming-book/ch1-basic/ch1-01-genesis.html
作为Go语言标志性的并发编程特性则来自于贝尔实验室的Tony Hoare于1978年发表的鲜为外界所知的关于并发研究的基础文献:顺序通信进程(communicating sequential processes ,缩写为CSP)。在最初的CSP论文中,程序只是一组没有中间共享状态的平行运行的处理过程,它们之间使用管道进行通信和控制同步。Tony Hoare的CSP并发模型只是一个用于描述并发性基本概念的描述语言,它并不是一个可以编写可执行程序的通用编程语言。
CSP并发模型最经典的实际应用是来自爱立信发明的Erlang编程语言。不过在Erlang将CSP理论作为并发编程模型的同时,同样来自贝尔实验室的Rob Pike以及其同事也在不断尝试将CSP并发模型引入当时的新发明的编程语言中。他们第一次尝试引入CSP并发特性的编程语言叫Squeak(老鼠的叫声),是一个用于提供鼠标和键盘事件处理的编程语言,在这个语言中管道是静态创建的。然后是改进版的Newsqueak语言(新版老鼠的叫声),新提供了类似C语言语句和表达式的语法,还有类似Pascal语言的推导语法。Newsqueak是一个带垃圾回收的纯函数式语言,它再次针对键盘、鼠标和窗口事件管理。但是在Newsqueak语言中管道已经是动态创建的,管道属于第一类值、可以保存到变量中。然后是Alef编程语言(Alef也是C语言之父Ritchie比较喜爱的编程语言),Alef语言试图将Newsqueak语言改造为系统编程语言,但是因为缺少垃圾回收机制而导致并发编程很痛苦(这也是继承C语言手工管理内存的代价)。在Aelf语言之后还有一个叫Limbo的编程语言(地狱的意思),这是一个运行在虚拟机中的脚本语言。Limbo语言是Go语言最接近的祖先,它和Go语言有着最接近的语法。到设计Go语言时,Rob Pike在CSP并发编程模型的实践道路上已经积累了几十年的经验,关于Go语言并发编程的特性完全是信手拈来,新编程语言的到来也是水到渠成了。