12

Gul Agha 在他的技术报告“Actor:分布式系统中的并发计算模型”中很好地描述了 Actor 模型。

在第 49 页,他解释了“成为”命令:

become <expression>

调用“成为 X”后,演员会将他所有的消息转发到另一个演员的邮箱 (X)。

但是,我不确定这是如何在 Erlang 和 Scala 等语言中实现的(它是如何实现的)。我必须手动编码吗?效率呢?Agha 展示了使用消息传递的堆栈实现。每次执行pop或push时,都会向某个actor添加一个转发链接......经过数十万次操作后,我希望这样的实现会花费太多时间转发消息而不做实际工作,除非有些在引擎盖下进行了很好的优化。

所以我的问题是:转发(或“成为”)是如何在典型的参与者语言中实现的,比如 Erlang、Scala(以及其他语言的库)?

4

5 回答 5

7

它不是直接在 Erlang 中实现的,但是您可以编写一个简单的become函数来接收消息,将其转发给另一个进程,然后调用自身:

become(Pid) ->
    receive
        Msg -> Pid ! Msg
    end,
    become(Pid).

(工业级版本可能需要处理信号和其他零碎的东西,但这是它的本质。)

调用将从外部世界的角度become(Pid)有效地将调用过程转变为过程。Pid

这并没有解决您强调的问题,反复调用become导致转发链增长。但是,这在 Erlang 中通常不会发生,我不确定 Erlang 的进程如何映射到 Actor 模型。

于 2010-07-24T13:40:07.800 回答
4

Actor 是一个逆变协函子,所以“成为”只是 comap。

换句话说,T 类型消息上的 Actor 基本上是类型 (T => Unit) 的函数。这只是简单的函数组合(也许是恒等函数)。

它在 Scalaz 中实现:

val a1 = actor(a => println(a))
val a2 = a1.comap(f)

演员 a2 将 f 应用于其消息,然后将结果发送给 a1。

于 2010-07-24T23:12:33.277 回答
4

在这里寻找Erlang。

在基本级别上,有两个选项可用。如果您只想become用来更改给定进程的行为(参见第 2.1.3 节列表的第 2 点),那么只需使用不同的递归函数调用下一个循环即可:

loop(State) ->
     receive
          {normal, Msg} -> loop(State);
          {change, NewLoop} -> NewLoop(State)
     end.

假设NewLoop是一个高阶函数,每当您将消息发送{change, NewLoop}到最初运行该函数的进程时loop/1,它将NewLoop用作其定义。

第二个选项是您希望进程充当代理(并更改行为)的选项。这与 Marcelo Cantos 的建议类似。只需让流程循环并将消息转发到新的(窃取他的代码):

become(Pid) ->
    receive
        Msg -> Pid ! Msg
    end,
    become(Pid).

从理论上讲,这可以满足论文的要求。然而,在实践中,在现实生活中的 Erlang 系统中使用第二个选项是有风险的。在两个进程之间的通信中,使用发送者的进程标识符“标记”消息是一个常见的概念,并且回复将使用接收者的进程标识符进行标记。可以交换以下消息(这不是代码,只是手写符号):

A = <0.42.0> <-- process identifier
B = <0.54.0>,
A: {A, "hello"},
B: {B, "hi"},
A: {A, "how are you?"}.
B: {B, "Fine!"}.

因此,当A期望来自 的消息时B,它将能够通过使用诸如{B, Message}. 在转发消息的情况下,该寻址方案变得无效并且被简单地破坏。

另一种方法是使用引用 ( make_ref()) 作为寻址方案来匹配返回的 Pid 之上的消息。这将通过使用两个不同的实体将 Pid 作为地址和标识符的使用分开。

即使寻址是安全的,还有另一个问题:进程依赖性。命名进程、崩溃进程、监视器等会发生什么?客户端进程可能有监视器、链接和其他所有设置,以确保在没有得到通知的情况下不会出现任何问题。通过修改路由过程以捕获退出信号并转发它们,应该可以使事情变得更安全:

loop(State) ->
     receive
          {normal, Msg} -> loop(State);
          {become, Pid} ->
              process_flag(trap_exit, true), % catch exit signals as messages
              link(Pid),                     % make it so if one process crashes, the other receives the signal
              become(Pid)
     end.

become(Pid) ->
    receive
        {'EXIT', Pid, killed} -> exit(self(), kill);  %% uncatchable termination
        {'EXIT', Pid, normal} -> ok;                  %% die normally too
        {'EXIT', Pid, Reason} -> exit(Reason);        %% die for the same reason as Pid
        Msg -> Pid ! Msg                              %% forward the message
    end,
    become(Pid).

Pid这个经过测试的代码应该更安全,因为依赖于第一个进程的进程将收到与in表示的相同的错误消息become(Pid),从而使路由变得相当透明。但是,我不能保证这在现实生活中的应用程序中长期有效。

尽管有可能并且在概念上足够简单来表示和执行类似的操作become,但 Erlang 的标准库并没有真正考虑到第二个用例。对于现实世界的应用程序,我只能推荐第一种方法,它被目前存在的每个 Erlang 应用程序广泛使用。第二个不常见,可能会导致问题


* *最后一个示例中对函数的调用become/1可能是?MODULE:become(Pid)为了避免将来与热代码加载相关的潜在崩溃。*

于 2010-07-25T13:59:44.627 回答
3

Akka 的 Actor 有一个“HotSwap”概念,您可以在其中向 Actor 发送一个新的 PartialFunction 来替换其现有的消息处理程序。前一个被记住并且可以恢复。在http://doc.akkasource.org/actors上搜索“hotswap”了解详情。

于 2010-07-25T15:02:13.533 回答
3

查看 Agha 的论文“Actors: A Model of Concurrent Computation in Distributed System”的第 12 页(我拥有的 PDF 副本的第 26 页)。“成为”是他的演员语言是你如何指定#2,演员的新行为。将消息转发给另一个参与者只是许多可能的新行为之一。

如果你想要转发行为,我认为使用 Scala 演员与使用 Erlang 基本上是同一条船。在幕后,Scala“react”和“reactWithin”的工作方式很像,因为react块定义的部分函数是actor的新行为,但我不确定相似性是否是故意的。

大多数(全部?)“演员”实现与 Hewitt 的演员模型和 Agha 的演员语言有很大的不同。IIRC 用 Agha 的语言指定演员行为的语言部分甚至都不是图灵完整的。只有当您考虑参与者的配置时,整个语言才会成为图灵完备的。我想说演员模型和当前演员框架之间的关系有点像 SmallTalk 中的面向对象与 C++ 中的面向对象的关系。有一些概念转移和类似的术语,但在细节上它们非常非常不同。

于 2010-07-24T17:38:20.090 回答