Actor模式

发布时间:2021-05-18 08:50
最后更新:2021-05-18 08:50
所属分类:
架构知识 软件设计理论

Actor模式是于1973年提出的,作为一个分布式并发编程模型,在Erlang中得到了广泛的应用。在Actor模式中,Actor是一个并行计算的模型,是设计用于大量独立处理器联合进行并行计算的。要理解Actor模式,借用面向对象的一句话就可以了:万物皆Actor。

原理

Actor模式将Actor作为通用原语,Actor作为参与者需要对接收到的消息做出响应,同时也可以向更多的参与者发出消息。Actor模式实际上是一个用于并行计算的概念模型,它所定义的是系统的组件需要如何动作和如何交互,以及它们做需要遵循的规则。

在Actor模式中,每个Actor都是完全独立的,就像是舞台上的一个个演员一样,可以同时执行它们的操作。Actor之间通过消息来与外界通信,这也就像是舞台上的演员通过语言相互交流一样。Actor之间的交互消息是异步的,但Actor处理消息的过程是同步的,而且一次只能处理一条消息。基于Actor的这个特点,每个Actor都会有自己的一个邮箱,这个邮箱用于缓存其他Actor发来的消息,这样可以使其他Actor发送过来的消息不至于丢失。

一个Actor在接收到消息以后,可以执行的动作通常有以下三个:

  • 发送其他消息给其他的Actor,不限于仅发送一条消息。
  • 创建一系列新的Actor。
  • 为下一个接收到的信息指定行为。

Actor在完成这三个动作时没有固定的顺序,这也是并发编程的特点。Actor会根据接收到的消息做出不同的处理行为。

Actor模式的另一个优势是可以消除共享状态。传统多线程并发编程的数据共享是通过共享内存区域来实现的,在访问共享内存区域的时候,往往需要加锁来使线程获得共享内存区域的临时独占权。加锁的操作不仅需要消耗时间,而且还会带来一些无法预料的问题,例如死锁。而Actor模式则不同,Actor每次只能处理一条消息,所以在Actor内部可以安全的处理状态,不必使用锁的机制。

一个Actor是由状态(state)、行为(behavior)和邮箱(mailbox)组成的,Actor中的着三种元素都有以下特性:

  • 状态是Actor对象内部所存储的信息,仅由Actor自身进行管理。
  • 行为是Actor对象中的业务逻辑,可以通过Actor接收到的消息改变Actor的状态。
  • 邮箱是Actoe对象之间的通信桥梁,在实现上,邮箱实际上就是一个FIFO的消息队列。

通过Actor对象中这三种元素的特征,可以看出Actor模式都使用哪些原则来解决并发编程中的痛点。

  1. 所有的Actor对象都是本地的,无法从外部访问。
  2. 所有的Actor必须通过消息进行通信。
  3. Actor的行为主要包括响应消息、退出Actor、改变Actor的内部状态、发送消息到一个或者多个Actor。
  4. Actor可能会阻塞自己的运行,但是不能阻塞所在的线程。

当所有的并发参与者都按照以上原则进行工作时,并发编程中的那些诸如共享内存访问、死锁等问题就都将得到解决。

调度流程

在使用Actor模式的系统中,首先会存在一个未处理的任务集,这个任务集中的每个任务都有至少以下三个属性标识:

  • 任务的标记tag用以将任务与其他的任务区别开。
  • 任务要送达的目的地target,用于标识任务需要送达的邮箱。
  • 任务的通讯信息communication,用于向Actor提供处理任务所需要的信息,也同时包含用来处理任务的Actor本身。

这三个属性组成了一个任务的基本元素,并且可以在Actor之间传递,所以一个任务也可以被看作是一条消息。对于一条消息的处理,Actor通常都需要进行调度,Actor模式通常会有两种调度模式,一种基于线程,一种基于事件。

基于线程的调度方式比较容易理解,就是将每个Actor都放入一个线程里。如果这个Actor要处理的消息队列是空的,那么这个Actor的线程将被阻塞在等待消息输入的位置上。但是在这种情况下,一个系统中可以创建和控制的线程数量是有限的,而且系统在不同线程之间进行切换也是有资源成本的,所以大多数Actor模式都没有选择基于线程的调度方式。

而基于事件的调度方式则不同,虽然每个Actor也是运行在一个独立的线程中,但是只有在这个Actor接收到消息的时候,系统才会为其分配线程。这样,系统就可以使用比较少的线程资源创建和管理更多的Actor。所以对于一个Actor,其在运行的时候会是以下这样一个状态循环。

@startuml
hide empty description
state "等待消息" as waiting
state "系统分配线程" as dispatch
state "处理消息" as processing
state "发送消息给其他Actor" as sending
state "释放占据线程" as dispose

waiting -> dispatch: 收到消息
dispatch -> processing
processing -> sending
sending -> dispose
dispose -> waiting
@enduml

于是,根据前文的Slogan:万物皆Actor,可以把一个系统中的任务分解为一些更细力度的小任务,每个任务交由一个Actor去处理,这样在一些可以并行处理的场合,Actor模式就会自然的形成并发,减少任务的完成时间。而且因为Actor之间不需要访问共享内存区域,所以Actor中的处理过程也无须考虑锁的机制。


索引标签
架构知识
软件设计理论
Actor
高并发
共享内存
分布式