11-并发编程之-并发模型之Actor和CSP
一 传统并发和基于消息传递的并发
在多核CPU机器下,为了充分利用计算机的资源,我们需要进行并发编程
1.1 传统并发模型
多线程编程,就是传统的并发编程模式
传统的多线程编程,使用的是ShreadMemory(共享内存)的方式,来实现的
有并发的地方就有竞争,传统多线程的并发模式使用lock(锁),condition (条件变量)等同步原语来强制规定了进程的执行顺序,而这些同步原语本质上都是在各个线程中使用了锁来来实现
除了基于shared memory(共享内存)以外,还有什么其他的并发模型吗?
答案是有的,就是基于消息的并发模型
1.2 基于消息的并发模型
基于消息传递(Message Passing)的并发模型CSP和Actor
这两种模型很像,但还是有一些不同的地方
Actor模型:在Actor模型中,主角是Actor,类似一种worker,Actor彼此之间直接发送消息,不需要经过什么中介,消息是异步发送和处理的

CSP模型:CSP模型中,worker之间不直接彼此联系,而是通过不同channel进行消息发布和侦听。消息的发送者和接收者之间通过Channel松耦合,发送者不知道自己消息被哪个接收者消费了,接收者也不知道是哪个发送者发送的消息

二 CSP介绍
CSP的是Communicating Sequential Processes (CSP)的缩写,翻译成中文是顺序通信进程,简称CSP
CSP的核心思想是多个线程之间通过Channel来通信,对应到golang中的chan结构,对应到Python中是Queue
Go语言的CSP模型是由协程Goroutine与通道Channel实现:
- Go协程goroutine: 是一种轻量线程,它不是操作系统的线程,而是将一个操作系统线程分段使用,通过调度器实现协作式调度。是一种绿色线程,微线程,它与Coroutine协程也有区别,能够在发现堵塞后启动新的微线程。
- 通道channel: 类似Unix的Pipe,用于协程之间通讯和同步。协程之间虽然解耦,但是它们和Channel有着耦合
三 Actor介绍
Actor模式有一点类似面向对象模型,世界上所有的东西都被命名为Actor。
单个Actor会拥有一些状态,比如为名字是book的Actor可能被描述为:
name : 西游记
author : 吴承恩
price : 99
到此为止好像和对象object没有什么不同,但是Actor不会给外界提供任何的行为接口.
比如book.getPrice()这是很自然的面向对象的写法,在Actor是不被允许的。
每一个Actor的属性绝不对外暴露,想和外界进行通信必须发送message,所以每个Actor自身都有一个邮箱(MailBox)
Actor模型描述了一组为了避免并发编程的常见问题的公理:
1.所有Actor状态是Actor本地的,外部无法访问。
2.Actor必须只有通过消息传递进行通信。
3.一个Actor可以响应消息:推出新Actor,改变其内部状态,或将消息发送到一个或多个其他参与者。
4.Actor可能会堵塞自己,但Actor不应该堵塞它运行的线程。
四 CSP与Actor的区别
Actor模型和CSP区别图如下:

Actor之间直接通讯,而CSP是通过Channel通讯,在耦合度上两者是有区别的,后者更加松耦合。
4.1主要的不同点
关于消息发送方和接收方
Actor:注重的处理单元,也就是Actor,而不是消息传送方式。发送消息时,都需要知道对方是谁。
这里的“都需要知道对方是谁”的意思,当ActorX要给ActorY发消息时,必须明确知道ActorY的地址。ActorY接收到消息时,就能够知道消息发送者(ActorX)的地址。返回消息给发送者时,只需要按发送者的地址往回传消息就行。
CSP:注重的是消息传送方式(channel),不关心发送的人和接收的人是谁。
向channel写消息的人,不知道消息的接收者是谁;读消息的人,也不知道消息的写入者是谁。
两者比较看来,CSP把发送方和接收方给解耦了,但这种解耦带的好处是什么呢?
消息传输方式
Actor:每一对Actor之间,都有一个“MailBox”来进行收发消息。消息的收发是异步的。
CSP:使用定义的 channel 进行收发消息。消息的收发是同步的(也可以做成异步的,但是一个有限异步)
Actor 模式消息传输,只有一个通道(MailBox),所以无论什么“类型”的消息都可能发过来,所以要做好模式配置。而 CSP 中的通道(channel)类型是定好的,而且两个对象可以可以使用多个通道传输消息。(CSP 把通信给细化了,让你在通信时有多种选择,例如:用一个 channel 传一类数据,用另一个 channel 传另一类数据)
这就和MQ的机制有点像了。在通过MQ传输消息时有两种选择:
选择把这个消息发送到哪个 Exchange(类似 channel)里,对于不同的 Exchange 可以有不同的处理程序。
还可以把数据发送到一个 Exchange 里,然后设置分发规则,选择不同的处理程序。
4.2 从写程序的角度来看,不同点
要做【关于取得RSS文章的单词数】这样的程序
CSP
步骤1:定义几个channel,保存不同处理的之间的数据:
1 新文章ch
2 文章内容ch
3 单词数ch
步骤2:然后写相应的处理程序:
1 新文章URL处理(把得取的新文章的URL,写入“新文章ch”)
2 新文章内容处理(把新文章内容读取下来,写入“文章内容ch”)
3 单词数统计(对文章内容进行单词个数统计,写入“单词数ch”)
4 文章单词数累加(读取“单词数ch”,把各个文章单词数进行累加)
步骤3:在主程序中,定义上面的几个channel,再调用几个处理程序,并把channel当成参数传给处理程序
Actor
步骤1:定义控制Actor,控制程序流程,功能如下:
1 当收到指令是“新文章URL处理”的话,调用“新文章URL处理”Actor,并把处理结果返回给调用者。
2 当收到指令是“新文章内容处理”的话,调用“新文章内容处理”Actor,并把处理结果返回给调用者。
3 当收到指令是“单词数统计”的话,调用“单词数统计”Actor,并把处理结果返回给调用者。
4 当收到指令是“文章单词数累加”的话,调用“文章单词数累加”Actor,并把处理结果返回给调用者。
(以上的每一个指令的执行,都可以做成并行的)
步骤2:定义指令相对应的处理程序。
步骤3:主程序,向“控制Actor”发指令,并把每次指令的结果当成参数,传给下一次的指令调用。
需求增加,在单词数统计时,去掉”of/to/and”这些单词,不同实现
CSP
主程序:
1 定义一个新的ch
2 并增加对内容过滤程序的调用。
3 传给“单词数统计”程序的ch,要修改成新定义的ch
子程序:
1 新加一个处理程序。
Actor
主程序:
1 增加对内容过滤Actor的调用
控制Actor:
1 增加一个新的内容过滤指令
子程序:
1 定义一个新的内容过滤Actor
4.3 总结
它们都是描述独立的流程通过消息传递进行通信
主要的区别在于:在CSP消息交换是同步的(即两个流程的执行"接触点"的,在此他们交换消息),而Actor模型是完全解耦的,可以在任意的时间将消息发送给任何未经证实的接受者。
由于Actor享有更大的相互独立,因为他可以根据自己的状态选择处理哪个传入消息。自主性更大些。
在Go语言中为了不堵塞流程,程序员必须检查不同的传入消息,以便预见确保正确的顺序。CSP好处是Channel不需要缓冲消息,而Actor理论上需要一个无限大小的邮箱作为消息缓冲
无论CSP或者Actor,他们都贯彻了一句话:
Don't communicate by sharing memory; share memory by communicating.
作者:liuqingzheng
出处:https://www.cnblogs.com/liuqingzheng/p/15997432.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人