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

演员模型在1973年于Carl Hewitt英语Carl Hewitt、Peter Bishop及Richard Steiger的论文中提出[1]。它已经被用作并发计算理论理解英语Actor model theory框架和并发系统实际实现英语Actor model implementation基础。演员模型和其他类似工作的关系讨论可见于演员模型和进程演算英语actor model and process calculi

基本概念

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

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

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

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

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

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

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

历史

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

在Hewitt、Bishop和Steiger的1973年刊物之后,Irene Greif英语Irene Greif在1975年博士论文中,为演员模型开发出了一种操作语义[4]Henry Baker英语Henry Baker和Hewitt在1977年发表了演员系统的公理法则[5][6]。其他主要的里程碑包括:William Clinger英语William Clinger (computer scientist)的1981年学位论文,它介入了基于幂域英语Power domains指称语义[3];还有Gul Agha英语Gul Agha的1985年学位论文,它进一步发展出基于transition的语义模型,从而补充了Clinger的模型[7]。这些工作促成了演员模型理论英语actor model theory的全面发展。

主要的软件实现工作,由麻省理工学院消息传递语义小组完成,其成员包括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领导的研究小组,致力于构造新的电脑架构,用以进一步发展演员模型中的消息传递。有关工作详见演员模型实现英语Actor model implementation

演员模型的研究,已经开展于加州理工学院京都大学微电子及电脑技术公司英语Microelectronics and Computer Technology CorporationMIT人工智慧实验室斯坦福国际研究所斯坦福大学伊利诺伊大学厄巴纳-香槟分校[8]巴黎第六大学比萨大学东京大学米泽日语米澤明憲研究室、荷兰数学和电脑科学研究学会和其他一些地方。

使用演员模型编程

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

早期的演员模型编程语言

后期的演员模型编程语言

演员模型库及框架

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

More information 名称, 状态 ...
名称 状态 最新发行 许可证 语言
ReActed[26] 活跃 2022-11-30 Apache 2.0 Java
Acteur[27] 活跃 2020-04-16[28] Apache-2.0 / MIT Rust
Bastion[29] 活跃 2020-08-12[30] Apache-2.0 / MIT Rust
Actix[31] 活跃 2019-05-30[32] MIT Rust
Aojet[33] 活跃 2016-10-17 MIT Swift
Actor[34] 活跃 2017-03-09 MIT Java
Actor4j[35] 活跃 2020-01-31 Apache 2.0 Java
Actr[36] 活跃 2019-04-09[37] Apache 2.0 Java
Vert.x[38] 活跃 2018-02-13 Apache 2.0 Java, Groovy, Javascript, Ruby, Scala, Kotlin, Ceylon
ActorFx[39] 不活跃 2013-11-13 Apache 2.0 .NET
Akka 活跃 2022-09-06[40] Apache 2.0 Java and Scala
Akka.NET[41] 活跃 2020-08-20[42] Apache 2.0 .NET
Apache Pekko[43] 活跃 2023-07-26[44] Apache 2.0 Java and Scala
Remact.Net[45] 不活跃 2016-06-26 MIT .NET, Javascript
Ateji PX[46] 不活跃 ? ? Java
czmq[47] 活跃 2016-11-10 MPL-2 C
F# MailboxProcessor 活跃 同于F# (内建核心库) Apache License F#
Korus[48] 活跃 2010-02-04 GPL 3 Java
Kilim[49][50] 活跃 2018-11-09[51] MIT Java
ActorFoundry (基于Kilim) 不活跃 2008-12-28 ? Java
ActorKit[52] 活跃 2011-09-13[53] BSD Objective-C
Cloud Haskell[54] 活跃 2015-06-17[55] BSD Haskell
CloudI[56] 活跃 2023-10-27[57] MIT ATS, C/C++, Elixir/Erlang/LFE, Go, Haskell, Java, Javascript, OCaml, Perl, PHP, Python, Ruby
Clutter[58] 活跃 2017-05-12[59] LGPL 2.1 C, C++ (cluttermm), Python (pyclutter), Perl (perl-Clutter)
NAct[60] 不活跃 2012-02-28 LGPL 3.0 .NET
Nact[61] 活跃 2018-06-06[62] Apache 2.0 JavaScript/ReasonML
Retlang[63] 不活跃 2011-05-18[64] New BSD .NET
JActor[65] 不活跃 2013-01-22 LGPL Java
Jetlang[66] 活跃 2013-05-30[67] New BSD Java
Haskell-Actor[68] 不活跃? 2008 New BSD Haskell
GPars[69] 活跃 2014-05-09[70] Apache 2.0 Groovy
OOSMOS[71] 活跃 2019-05-09[72] GPL 2.0和商业(双许可证) C. C++ friendly
Panini[73] 活跃 2014-05-22 MPL 1.1 自己的编程语言
PARLEY[74] 不活跃? 2007-22-07 GPL 2.1 Python
Peernetic[75] 活跃 2007-06-29 LGPL 3.0 Java
PostSharp[76] 活跃 2014-09-24 商业 / Freemium .NET
Pulsar[77] 活跃 2016-07-09[78] New BSD Python
Pulsar[79] 活跃 2016-02-18[80] LGPL/Eclipse Clojure
Pykka[81] 活跃 2019-05-07[82] Apache 2.0 Python
Termite Scheme[83] 不活跃? 2009-05-21 LGPL Scheme (Gambit实现)
Theron[84] 不活跃[85] 2014-01-18[86] MIT[87] C++
Thespian[88] 活跃 2020-03-10 MIT Python
Quasar[89] 活跃 2018-11-02[90] LGPL/Eclipse Java
Libactor[91] 不活跃? 2009 GPL 2.0 C
Actor-CPP[92] 活跃 2012-03-10[93] GPL 2.0 C++
S4[94] 不活跃 2012-07-31[95] Apache 2.0 Java
C++ Actor Framework (CAF)[96] 活跃 2020-02-08[97] Boost Software License 1.0 and BSD 3-Clause C++11
Celluloid[98] 活跃 2018-12-20[99] MIT Ruby
LabVIEW Actor Framework[100] 活跃 2012-03-01[101] National Instruments SLA[102] LabVIEW
LabVIEW Messenger Library[103] 活跃 2021-05-24 BSD LabVIEW
Otavia[104] 活跃 2024-01-02 Apache 2.0 Scala
Orbit[105] 活跃 2019-05-28[106] New BSD Java
QP框架 活跃 2019-05-25[107] GPL 2.0和商业(双许可证) C and C++
libprocess[108] 活跃 2013-06-19 Apache 2.0 C++
SObjectizer[109] 活跃 2021-12-28[110] New BSD C++11
rotor[111] 活跃 2022-04-23[112] MIT License C++17
Orleans[113] 活跃 2023-07-11[114] MIT License C#/.NET
Skynet[115] 活跃 2016-07-11 MIT License C/Lua
Reactors.IO[116] 活跃 2016-06-14 BSD License Java/Scala
libagents[117] 活跃 2020-03-08 Free software license C++11
Proto.Actor[118] 活跃 2021-01-05 Free software license Go, C#, Python, JavaScript, Java, Kotlin
FunctionalJava[119] 活跃 2018-08-18[120] BSD 3-Clause Java
Riker[121] 活跃 2019-01-04 MIT License Rust
Comedy[122] 活跃 2019-03-09 EPL 1.0 JavaScript
VLINGO XOOM Actors[123] 活跃 2023-02-15 Mozilla Public License 2.0 Java, Kotlin, JVM languages, C# .NET
wasmCloud[124] 活跃 2021-03-23 Apache 2.0 WebAssembly (Rust, TinyGo, Zig, AssemblyScript)
ray[125] 活跃 2020-08-27 Apache 2.0 Python
DOTNETACTORS[126] 活跃 2021-06-14 MIT .NET, C#, Azure Service Bus
go-actor[127] 活跃 2022-08-16 GPL 3.0 Golang
Sento[128] 活跃 2022-11-21 Apache 2.0 Common Lisp
Xcraft Goblins[129] 活跃 2022-08-30 MIT JavaScript
Tarant[130] 活跃 2023-04-17 MIT Typescript, Javascript
Close

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

并发编程语言用例

尽管Erlang语言设计者并未如此表述[131],因其进程间通信是通过无共享英语Shared-nothing architecture异步英语Asynchrony (computer programming)消息传递系统运作,它一般被引证为采用演员模型的典型代表之一。在Erlang中,所有进程都有一个自己的“邮箱”,它是从其他进程已经发送过来而仍未被消费的消息的队列。进程使用receive原语来检索匹配预期模式的消息。一个消息处理例程针对每个模式依次测试这些消息,直到其中有一个匹配。在消息被消费并从邮箱中移除之时进程恢复执行。消息可以包含任何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年发表了一个原型的演员编程语言,用意在于直接表达演员行为的重要方面[132]。消息采用如下表示法:

<标签>[<元素>1 ... <元素>n]

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

环境演员

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

  • 当一个environment演员收到Request[Bind[identifier value] customer]的时候,建立一个新的环境演员environment’发送给customer,使得这个新环境演员收到Request[Lookup[identifier’] customer’]的时候,如果identifier同于identifier’,则发送给customer’一个Returned[value],否则发送给environment一个Request[Lookup[identifier’] customer’]
  • 当一个environment演员收到Request[Bind[<模式> String] customer]的时候,如果此<模式>形如Request[msg[paramerer] customer],匹配于String形如Request[msg[argument] customer],则建立一个新的环境演员environment’发送给customer,使得这个新环境演员收到Request[Lookup[parameter’] customer’]的时候,如果parameter’同于parameter,则发送给customer’一个Returned[argument],否则发送给customer一个Thrown[NotFound[<模式>]]
  • 当一个environment演员收到Request[Bind[identifier(parameter) value] customer]的时候,建立一个新的环境演员environment’发送给customer,使得这个新环境演员收到Request[Lookup[identifier’(argument)] customer’]的时候,如果identifier同于identifier’,则建立一个新的环境演员environment’’,发送给customer’一个Returned[value]和一个Returned[environment’’],否则发送给environment一个Request[Lookup[identifier’(argument)] customer’]。这个新环境演员environment’’在收到Request[Lookup[parameter’] customer’]的时候,如果parameter’同于parameter,则发送给customer’一个Returned[argument],否则发送给environment’一个Request[Lookup[parameter’] customer’]

上述环境演员建造在EmptyEnvironment演员之上,它在接收到Request[Lookup[identifier] customer]的时候,发送给customer一个Thrown[NotFound[identifier]]。当它收到Bind请求的时候,EmptyEnvironment表现的如同上述环境演员。

表达式

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

<标识符>
在收到Request[Eval[environment] customer]的时候,发送给environment一个Request[Lookup[<标识符>] customer]
send <接收者> <通信>
在收到Request[Eval[environment] customer]的时候,建立一个新演员evalCustomer1,发送给<接收者>一个Request[Eval[environment] evalCustomer1],使得
evalCustomer1收到通信Returned[theRecipient]的时候,建立一个新演员evalCustomer2,发送给<通信>一个Request[Eval[environment] evalCustomer2],使得
evalCustomer2收到通信Returned[theCommunication]的时候,发送给theRecipient一个theCommunication
<接收者>.<消息>
在收到Request[Eval[environment] customer]的时候,建立一个新演员evalCustomer1,发送<接收者>一个Request[Eval[environment] evalCustomer1],使得
evalCustomer1收到通信Returned[theRecipient]的时候,建立一个新演员evalCustomer2,发送给<消息>一个Request[Eval[environment] evalCustomer2],使得
evalCustomer2收到通信Returned[theMessage]的时候,发送给theRecipient一个Request[theMessage customer]
receiver ... <模式>i <表达式>i ...
在收到Request[Eval[environment] customer]的时候,发送给customer一个新演员theReceiver,使得
theReceiver收到通信内容com的时候,建立一个新演员bindingCustomer,并发送给environment一个Request[Bind[<模式>i com] bindingCustomer],而且
  1. 如果bindingCustomer收到Returned[environment’],发送给<表达式>i一个Request[Eval[environment’]]
  2. 不然如果bindingCustomer收到Thrown[...],尝试<模式>i+1
behavior ... <模式>i <表达式>i ...
在收到Request[Eval[environment] customer]的时候,发送给customer一个新演员theReceiver,使得
theReceiver收到Request[message customer’]的时候,建立一个新演员bindingCustomer,并发送给environment一个Request[bind[<模式>i message] customer’],而且
  1. 如果bindingCustomer收到Returned[environment’],发送给<表达式>i一个Request[Eval[environment’] customer’]
  2. 不然如果bindingCustomer收到Thrown[...],尝试<模式>i+1
{<表达式>1, <表达式>2}
在收到Request[Eval[environment] customer]的时候,发送给<表达式>1一个Request[Eval[environment]],而且并发的发送给<表达式>2一个Request[Eval[environment] customer]
let <标识符> = <表达式> in <表达式>
在收到message[Eval[environment] customer]的时候,建立一个新演员evalCustomer,并发送给<表达式>一个Request[Eval[environment] evalCustomer]
evalCustomer收到Returned[theValue]的时候,建立一个新演员bindingCustomer,并发送给environment一个Request[bind[<标识符> theValue] bindingCustomer]
bindingCustomer收到Returned[environment’]的时候,发送给<expression>一个Request[Eval[environment’] customer]
serializer <表达式>
在收到Request[Eval[environment] customer]的时候,发送给customer一个Returned[theSerializer],这里的theSerializer是新演员,使得发送到theSerializer的通信按FIFO次序由行为演员处理,行为演员初始是<表达式>.Eval[environment],而且
theSerializer收到通信内容com的时候,建立一个新演员customer’,发送给行为演员一个Request[com customer’],使得
customer’收到Returned[value]Returned[theNextBehavior]的时候,Returned[value]被发送给customer,而theNextBehaviortheSerializer用作下次通信的行为演员。

例子程序

下面是简单的存储单元格(cell)的例子脚本(script),它可以包含任何演员地址:

Cell ≡
receiver
Request[Create[initial] customer]
send customer Returned[serializer ReadWrite(initial)]

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

ReadWrite(contents) ≡
behavior
Request[read[] customer]
{send customer Returned[contents], Returned[ReadWrite(contents)]}
Request[write[x] customer]
{send customer Returned[], Returned[ReadWrite(x)]}

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

let x = Cell.Create[5] in {x.write[7], x.write[9], x.read[]}

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

影响

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

理论

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

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

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

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

实践

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

参见

引用

延伸阅读

外部链接

Wikiwand in your browser!

Seamless Wikipedia browsing. On steroids.

Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.

Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.