【问题标题】:How is "become" implemented in languages that support the actor model?如何用支持演员模型的语言实现“成为”?
【发布时间】:2011-03-20 12:14:50
【问题描述】:

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

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

become <expression>

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

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

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

【问题讨论】:

    标签: scala concurrency erlang actor


    【解决方案1】:

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

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

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

    调用become(Pid)将有效地将调用进程从外部世界的角度变成进程Pid

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

    【讨论】:

      【解决方案2】:

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

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

      在 Scalaz 中实现:

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

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

      【讨论】:

      • 函数组合语义似乎与此页面上的 become 的其他描述不一致,我读到的意思是一个参与者的语义替换了另一个参与者的语义,而不是添加到它。跨度>
      • 好的,所以将新演员分配给同一个变量。
      【解决方案3】:

      在这里使用 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).
      

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

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


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

      【讨论】:

        【解决方案4】:

        查看 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++ 中的面向对象的关系。有一些概念转移和类似的术语,但在细节上它们非常非常不同。

        【讨论】:

          【解决方案5】:

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

          【讨论】:

          猜你喜欢
          • 2011-02-08
          • 2022-11-14
          • 2011-04-16
          • 1970-01-01
          • 2012-05-06
          • 1970-01-01
          • 2018-06-23
          • 1970-01-01
          • 2014-12-07
          相关资源
          最近更新 更多