演员模型

在电脑科学中，演员模型（Actor model）是一种并行运算上的模型。“演员”是一种程式上的抽象概念，被视为并行运算的基本单元：当一个演员接收到一则讯息，它可以做出一些决策、建立更多的演员、传送更多的讯息、决定要如何回答接下来的讯息。演员可以修改它们自己的私有状态，但是只能通过消息间接的相互影响（避免了基于锁的同步）。

演员模型在1973年于Carl Hewitt、Peter Bishop及Richard Steiger的论文中提出。它已经被用作并发计算的演员模型理论框架和并发系统的演员模型实现基础。演员模型和其他类似工作的关系讨论可见于演员模型和进程演算。

## 基本概念

演员模型推崇的哲学是“一切皆是演员”，这与面向对象编程的“一切皆是对象”类似。

演员是一个运算实体，响应接收到的消息，相互间是并发的：

- 发送有限数量的消息给其他演员。
- 创建有限数量的新演员。
- 指定接收到下一个消息时要用到的行为。

以上动作不含有顺序执行的假设，因此可以并行进行。

发送者与已发送通信的解耦，是演员模型的根本优势，演员模型启用了异步通信并将控制结构当作消息传递的模式。

消息接收者是通过地址区分的，有时也被称作“邮件地址”。因此演员只能和它拥有地址的演员通信。它可以通过接收到的信息获取地址，或者获取它创建的演员的地址。

演员模型的特征是，演员内部或相互之间的计算本质上是并发性的，演员可以动态创建，演员地址包含在消息中，交互只有通过直接的异步消息传递通信，不限制消息到达的顺序。

## 历史

演员模型受到了Lisp、Simula、Smalltalk-72、基于权限的安全性和分组交换的影响。其发展“受到由几十、几百、甚至几千个独立微处理机构成的高度并行计算机器的前景所推动，其中的每个处理机都有自己局部内存和通信处理器，它们通过高性能网络进行通信。”此后随着采用多核和众核处理器计算机架构的大规模并发计算的出现，人们已经重新燃起了对演员模型的兴趣。

在Hewitt、Bishop和Steiger的1973年刊物之后，Irene Greif在1975年博士论文中，为演员模型开发出了一种操作语义。Henry Baker和Hewitt在1977年发表了演员系统的公理法则。其他主要的里程碑包括：William Clinger的1981年学位论文，它介入了基于幂域的指称语义；还有Gul Agha的1985年学位论文，它进一步发展出基于transition的语义模型，从而补充了Clinger的模型。这些工作促成了演员模型理论的全面发展。

主要的软件实现工作，由麻省理工学院的消息传递语义小组完成，其成员包括Russ Atkinson、Giuseppe Attardi、Henry Baker、Gerry Barber、Peter Bishop、Peter de Jong、Ken Kahn、Henry Lieberman、Carl Manning、Tom Reinhardt、Richard Steiger和Dan Theriault。分别由加州理工学院的Chuck Seitz和麻省理工学院的Bill Dally领导的研究小组，致力于构造新的计算机架构，用以进一步发展演员模型中的消息传递。有关工作详见演员模型实现。

演员模型的研究，已经开展于加州理工学院、京都大学、微电子及计算机技术公司、MIT人工智能实验室、斯坦福国际研究所、斯坦福大学、伊利诺伊大学厄巴纳-香槟分校、巴黎第六大学、比萨大学、东京大学米泽明宪研究室、荷兰数学和计算机科学研究学会和其他一些地方。

## 使用演员模型编程

一些编程语言使用了演员模型或变种。这些语言包括：

### 早期的演员模型编程语言

- Act 1，2，3
- Acttalk
- Ani
- Cantor
- Rosette

### 后期的演员模型编程语言

- 基于演员的并发语言
- AmbientTalk
- Axum (编程语言)
- CAL演员语言
- D
- E
- Elixir
- Encore
- Erlang
- Fantom (编程语言)
- Humus
- Io
- Pony
- Ptolemy计划
- P (编程语言)
- P#
- Rebeca (编程语言)
- Reia
- Rust
- SALSA
- Scala
- Scratch
- TNSDL

### 演员模型库及框架

演员模型库及框架，允许用户在没有内置演员模型的语言中进行编程。这些框架包括：

名称｜状态｜最新发行｜许可证｜语言

ReActed｜2022-11-30｜Apache 2.0｜Java

Acteur｜2020-04-16｜Apache-2.0 / MIT｜Rust

Bastion｜2020-08-12｜Apache-2.0 / MIT｜Rust

Actix｜2019-05-30｜MIT｜Rust

Aojet｜2016-10-17｜MIT｜Swift

Actor｜2017-03-09｜MIT｜Java

Actor4j｜2020-01-31｜Apache 2.0｜Java

Actr｜2019-04-09｜Apache 2.0｜Java

Vert.x｜2018-02-13｜Apache 2.0｜Java, Groovy, Javascript, Ruby, Scala, Kotlin, Ceylon

ActorFx｜不活跃｜2013-11-13｜Apache 2.0｜.NET

Akka｜2022-09-06｜Apache 2.0｜Java and Scala

Akka.NET｜2020-08-20｜Apache 2.0｜.NET

Apache Pekko｜2023-07-26｜Apache 2.0｜Java and Scala

Remact.Net｜不活跃｜2016-06-26｜MIT｜.NET, Javascript

Ateji PX｜不活跃｜?｜?｜Java

czmq｜2016-11-10｜MPL-2｜C

F# MailboxProcessor｜同于F# (内建核心库)｜Apache License｜F#

Korus｜2010-02-04｜GPL 3｜Java

Kilim｜2018-11-09｜MIT｜Java

ActorFoundry (基于Kilim)｜不活跃｜2008-12-28｜?｜Java

ActorKit｜2011-09-13｜BSD｜Objective-C

Cloud Haskell｜2015-06-17｜BSD｜Haskell

CloudI｜2023-10-27｜MIT｜ATS, C/C++, Elixir/Erlang/LFE, Go, Haskell, Java, Javascript, OCaml, Perl, PHP, Python, Ruby

Clutter｜2017-05-12｜ LGPL 2.1｜C, C++ (cluttermm), Python (pyclutter), Perl (perl-Clutter)

NAct｜不活跃｜2012-02-28｜ LGPL 3.0｜.NET

Nact｜2018-06-06｜Apache 2.0｜JavaScript/ReasonML

Retlang｜不活跃｜2011-05-18｜New BSD｜.NET

JActor｜不活跃｜2013-01-22｜LGPL｜Java

Jetlang｜2013-05-30｜New BSD｜Java

Haskell-Actor｜不活跃？｜2008｜New BSD｜Haskell

GPars｜2014-05-09｜Apache 2.0｜Groovy

OOSMOS｜2019-05-09｜GPL 2.0和商业(双许可证)｜C. C++ friendly

Panini｜2014-05-22｜MPL 1.1｜自己的编程语言

PARLEY｜不活跃？｜2007-22-07｜GPL 2.1｜Python

Peernetic｜2007-06-29｜LGPL 3.0｜Java

PostSharp｜2014-09-24｜商业 / Freemium｜.NET

Pulsar｜2016-07-09｜New BSD｜Python

Pulsar｜2016-02-18｜LGPL/Eclipse｜Clojure

Pykka｜2019-05-07｜Apache 2.0｜Python

Termite Scheme｜不活跃？｜2009-05-21｜LGPL｜Scheme (Gambit实现)

Theron｜不活跃｜2014-01-18｜MIT｜C++

Thespian｜2020-03-10｜MIT｜Python

Quasar｜2018-11-02｜LGPL/Eclipse｜Java

Libactor｜不活跃？｜2009｜ GPL 2.0｜C

Actor-CPP｜2012-03-10｜GPL 2.0｜C++

S4｜不活跃｜2012-07-31｜Apache 2.0｜Java

C++ Actor Framework (CAF)｜2020-02-08｜Boost Software License 1.0 and BSD 3-Clause｜C++11

Celluloid｜2018-12-20｜MIT｜Ruby

LabVIEW Actor Framework｜2012-03-01｜National Instruments SLA｜LabVIEW

LabVIEW Messenger Library｜2021-05-24｜BSD｜LabVIEW

Otavia｜2024-01-02｜Apache 2.0｜Scala

Orbit｜2019-05-28｜New BSD｜Java

QP框架｜2019-05-25｜GPL 2.0和商业(双许可证)｜C and C++

libprocess｜2013-06-19｜Apache 2.0｜C++

SObjectizer｜2021-12-28｜New BSD｜C++11

rotor｜2022-04-23｜MIT License｜C++17

Orleans｜2023-07-11｜MIT License｜C#/.NET

Skynet｜2016-07-11｜MIT License｜C/Lua

Reactors.IO｜2016-06-14｜BSD License｜Java/Scala

libagents｜2020-03-08｜Free software license｜C++11

Proto.Actor｜2021-01-05｜Free software license｜Go, C#, Python, JavaScript, Java, Kotlin

FunctionalJava｜2018-08-18｜BSD 3-Clause｜Java

Riker｜2019-01-04｜MIT License｜Rust

Comedy｜2019-03-09｜EPL 1.0｜JavaScript

VLINGO XOOM Actors｜2023-02-15｜Mozilla Public License 2.0｜Java, Kotlin, JVM languages, C# .NET

wasmCloud｜2021-03-23｜Apache 2.0｜WebAssembly (Rust, TinyGo, Zig, AssemblyScript)

ray｜2020-08-27｜Apache 2.0｜Python

DOTNETACTORS｜2021-06-14｜MIT｜.NET, C#, Azure Service Bus

go-actor｜2022-08-16｜GPL 3.0｜Golang

Sento｜2022-11-21｜Apache 2.0｜Common Lisp

Xcraft Goblins｜2022-08-30｜MIT｜JavaScript

Tarant｜2023-04-17｜MIT｜Typescript, Javascript

注意这里没有列出全部框架和库。

## 并发编程语言用例

尽管Erlang语言设计者并未如此表述，因其进程间通信是通过无共享架构异步 (编程)消息传递系统运作，它一般被引证为采用演员模型的典型代表之一。在Erlang中，所有进程都有一个自己的“邮箱”，它是从其他进程已经发送过来而仍未被消费的消息的队列。进程使用`receive`原语来检索匹配预期模式的消息。一个消息处理例程针对每个模式依次测试这些消息，直到其中有一个匹配。在消息被消费并从邮箱中移除之时进程恢复执行。消息可以包含任何Erlang结构，包括原始类型（整数，浮点数、字符、原子）、元组、列表和函数。

下面例子展示了Erlang对分布式进程的内建支持：

```erlang
% 建立一个进程并启用函数web:start_server(Port, MaxConnections)
ServerProcess = spawn(web, start_server, [Port, MaxConnections]),

% 在机器RemoteNode上建立一个远程进程并启用函数web:start_server(Port, MaxConnections)
RemoteProcess = spawn(RemoteNode, web, start_server, [Port, MaxConnections]),

% (异步的)发送消息到ServerProcess。消息包含一个元组，它具有原子"pause"和数"10"。
ServerProcess ! {pause, 10},

% 接收发给这个进程的消息
receive
    a_message -> do_something;
    {data, DataContent} -> handle(DataContent);
    {hello, Text} -> io:format("Got hello message: ~s", [Text]);
    {goodbye, Text} -> io:format("Got goodbye message: ~s", [Text])
end.
```

## 原型的演员编程语言

Hewitt在2006年发表了一个原型的演员编程语言，用意在于直接表达演员行为的重要方面。消息采用如下表示法：

1 ... n

编程语言的语义是通过将每个程序构造确定为有自己行为的演员来定义的。执行是通过在执行期间让Eval消息在程序构造之间传递来建模的。

### 环境演员

每个Eval消息都有一个充当环境的演员的地址，它能够进行标识符与值的绑定（binding）。environment演员是不可变的（immutable），也就是不变更的。

- 当一个environment演员收到RequestBindidentifier value customer的时候，建立一个新的环境演员environment’发送给customer，使得这个新环境演员收到RequestLookupidentifier’ customer’的时候，如果identifier同于identifier’，则发送给customer’一个Returnedvalue，否则发送给environment一个RequestLookupidentifier’ customer’。
- 当一个environment演员收到RequestBind String customer的时候，如果此形如Requestmsgparamerer customer，匹配于String形如Requestmsgargument customer，则建立一个新的环境演员environment’发送给customer，使得这个新环境演员收到RequestLookupparameter’ customer’的时候，如果parameter’同于parameter，则发送给customer’一个Returnedargument，否则发送给customer一个ThrownNotFound。
- 当一个environment演员收到RequestBindidentifier(parameter) value customer的时候，建立一个新的环境演员environment’发送给customer，使得这个新环境演员收到RequestLookupidentifier’(argument) customer’的时候，如果identifier同于identifier’，则建立一个新的环境演员environment’’，发送给customer’一个Returnedvalue和一个Returnedenvironment’’，否则发送给environment一个RequestLookupidentifier’(argument) customer’。这个新环境演员environment’’在收到RequestLookupparameter’ customer’的时候，如果parameter’同于parameter，则发送给customer’一个Returnedargument，否则发送给environment’一个RequestLookupparameter’ customer’。

上述环境演员建造在EmptyEnvironment演员之上，它在接收到RequestLookupidentifier customer的时候，发送给customer一个ThrownNotFoundidentifier。当它收到Bind请求的时候，EmptyEnvironment表现的如同上述环境演员。

### 表达式

原型语言有如下种类的表达式，这里的通信包括Request...、Returned...和Thrown...，这里的消息包括Eval...、Bind...和Lookup...：

在收到RequestEvalenvironment customer的时候，发送给environment一个RequestLookup customer。

send：在收到RequestEvalenvironment customer的时候，建立一个新演员evalCustomer1，发送给一个RequestEvalenvironment evalCustomer1，使得

在evalCustomer1收到通信ReturnedtheRecipient的时候，建立一个新演员evalCustomer2，发送给一个RequestEvalenvironment evalCustomer2，使得

在evalCustomer2收到通信ReturnedtheCommunication的时候，发送给theRecipient一个theCommunication。

.：在收到RequestEvalenvironment customer的时候，建立一个新演员evalCustomer1，发送一个RequestEvalenvironment evalCustomer1，使得

在evalCustomer1收到通信ReturnedtheRecipient的时候，建立一个新演员evalCustomer2，发送给一个RequestEvalenvironment evalCustomer2，使得

在 evalCustomer2收到通信ReturnedtheMessage的时候，发送给theRecipient一个RequesttheMessage customer。

receiver ... i i ...：在收到RequestEvalenvironment customer的时候，发送给customer一个新演员theReceiver，使得

在theReceiver收到通信内容com的时候，建立一个新演员bindingCustomer，并发送给environment一个RequestBindi com bindingCustomer，而且

- 如果bindingCustomer收到Returnedenvironment’，发送给i一个RequestEvalenvironment’
- 不然如果bindingCustomer收到Thrown...，尝试i+1。

behavior ... i i ...：在收到RequestEvalenvironment customer的时候，发送给customer一个新演员theReceiver，使得

在theReceiver收到Requestmessage customer’的时候，建立一个新演员bindingCustomer，并发送给environment一个Requestbindi message customer’，而且

- 如果bindingCustomer收到Returnedenvironment’，发送给i一个RequestEvalenvironment’ customer’
- 不然如果bindingCustomer收到Thrown...，尝试i+1。

{1, 2}：在收到RequestEvalenvironment customer的时候，发送给1一个RequestEvalenvironment，而且并发的发送给2一个RequestEvalenvironment customer。

let = 值 in 体：在收到messageEvalenvironment customer的时候，建立一个新演员evalCustomer，并发送给值一个RequestEvalenvironment evalCustomer。

在evalCustomer收到ReturnedtheValue的时候，建立一个新演员bindingCustomer，并发送给environment一个Requestbind theValue bindingCustomer。

在bindingCustomer收到Returnedenvironment’的时候，发送给体一个RequestEvalenvironment’ customer。

serializer：在收到RequestEvalenvironment customer的时候，发送给customer一个ReturnedtheSerializer，这里的theSerializer是新演员，使得发送到theSerializer的通信按FIFO次序由行为演员处理，行为演员初始是.Evalenvironment，而且

在theSerializer收到通信内容com的时候，建立一个新演员customer’，发送给行为演员一个Requestcom customer’，使得

在customer’收到Returnedvalue和ReturnedtheNextBehavior的时候，Returnedvalue被发送给customer，而theNextBehavior被theSerializer用作下次通信的行为演员。

### 例子程序

下面是简单的存储单元格（cell）的例子脚本（script），它可以包含任何演员地址：

Cell ≡

receiver

RequestCreateinitial customer

send customer Returnedserializer ReadWrite(initial)

上述脚本将建立一个存储单元格，它采用的行为ReadWrite定义如下：

ReadWrite(contents) ≡

behavior

Requestread customer

{send customer Returnedcontents, ReturnedReadWrite(contents)}

Requestwritex customer

{send customer Returned, ReturnedReadWrite(x)}

例如，下列表达式建立一个单元格x，具有初始内容5，并接着并发的向它写值7和9。

let x = Cell.Create5 in {x.write7, x.write9, x.read}

上述表达式的值是5、7或9。

## 影响

演员模型在并发计算的理论发展和实践软件开发中都有影响。

### 理论

演员模型影响了π-演算和随后的进程演算的发展。在Robin Milner的图灵奖获奖演说中，他写到：

纯lambda演算现在只使用两种东西来建造：项和变量。我们在进程演算上也能实现同样的经济性吗？Carl Hewitt凭借其演员模型，很久以前就应对了这个挑战；他宣告了值、在值上的算子和进程，都应该是同一种东西：即演员。

这个目标打动了我，因为它蕴涵了表达式有着同质性和完整性 ... 但是很久以后我才明白了如何依据代数演算来达成这个目标 ...

因此本着Hewitt的精神，我们的第一步，就是要求由项指示或由名字访问的所有东西，包括值、寄存器、算子、进程、对象，都是同一种东西；它们都应当是进程。

### 实践

演员模型在商业实践中已经有了巨大的影响。例如，Twitter将演员用于可伸缩性应用。还有，Microsoft在其开发的异步代理库中使用了演员模型。