演員模型

在電腦科學中，演員模型（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在其開發的異步代理庫中使用了演員模型。