4

我是 erlang 新手,从 Joe Armstrong 的“Programming Erlang”开始教程。

我对 8.6 中选择性接收中提到的“保存队列”感到困惑。如果消息根本不匹配,为什么不直接丢弃呢?将其放回邮箱以供以后处理的目的是什么?如果这是默认行为,那些垃圾消息(意味着它们无法匹配)可能会导致性能损失,因为它们会在不释放的情况下累积。

我想知道我是否误解了这部分。我试图检查进程邮箱的内容以获得更好的理解,但我无法做到。

请帮忙,我将不胜感激任何证明一点的代码片段,谢谢。

4

3 回答 3

10

这本书的这一段描述了一个接收组所做的事情的细节。不要忘记,一个进程可以顺序处理多个接收块,因此一条与第一个接收块的任何条目都不匹配的消息将被放入保存队列(为了提高效率),因为:

  • 它永远不会匹配当前接收块的任何条目,
  • 只有当一个输入消息匹配一个条目或超时结束时,才能保证当前接收块完成。

当一个接收块完成时,保存队列按原始接收顺序放回邮箱中,因为下一个接收块有机会获得与“保存队列”消息之一匹配的条目。

一种用法是管理优先级:

loop() ->
   ...
   receive
      {high_prio,Msg} -> process_priority_message(Msg)
   after 0
      ok  % no priority message
   end,

   receive
      {low_prio,Msg} -> process_normal_message(Msg);
      Other -> process_unexpected_message(Other)
   end,
   ...
   loop()

此代码允许处理消息 {high_prio,Msg} 即使它不在消息队列中的第一个位置。

你是对的,有意外消息在邮箱中积累的风险,特别是在一个永无止境的循环中,这就是为什么你会经常看到最后一行的东西

其他-> process_unexpected_message(其他)

清空邮箱。

于 2013-02-07T10:07:58.447 回答
3

这在构建并发系统时有很大帮助,因为它允许您只专注于您当时感兴趣那些消息,而忽略其他消息。Erlang 系统通常是非确定性的,因此您很少知道将收到什么以及何时收到。如果没有自动保存队列,这意味着您在每次收到消息时都必须能够处理可能到达此过程的每条消息。它很快就变成了状态和消息的组合爆炸。

以简单的服务器为例。在其顶层将有一个接收循环,用于接收服务器处理的请求。然后它将处理第一个请求。在此处理过程中,它很可能会与其他进程通信并接收消息。在处理请求时,新的请求消息可以到达服务器。如果 Erlang 没有保存消息,那么您将不得不在接收消息的服务器代码中的任何地方处理这些请求。现在您可以忽略这些新请求并将它们留给应该处理它们的顶部循环。

快速跟踪需要在服务器某处处理的所有消息变得不可行。例如,在gen_server您有客户端发送的实际请求(未指定消息格式)、向服务器发送“系统”消息(未指定消息格式)、除所有消息处理您的请求需要。

您最终将实现自己的消息缓冲区并将其传递。

没有消息保存队列将使得编写在处理时发送/接收消息的通用模块实际上是不可能的,例如客户端函数到gen_servers。他们必须了解可能到达该流程并需要处理的每条消息。

是的,保存所有消息可能是个问题,但当您意识到这一点时,它通常是可以解决的问题类型。例如,在服务器的顶部循环中,您可以合理地确定可以接收和丢弃未知消息。Agen_server确实在它知道如何处理它们的顶层接收所有消息,其中一些消息由它自己处理(系统消息),而其他消息则传递给特定的服务器代码。

正如@Pascal 所示,它允许您轻松处理优先级消息。

于 2013-02-07T20:49:07.560 回答
2

+1 到 rvirding,“在构建并发系统时它是一个巨大的帮助”,这正是它的意义所在。让我想起了一些示例代码——一个“吸烟者问题”的解决方案——我不久前就开始了。我认为分享它可能有助于说明这一点:

-module(smokers).
-export([smokers/0]).

smokers() ->
    Rounds = 1000000,
    Agent = self(),
    lists:foreach(fun(Material) -> spawn(fun() -> startSmoker(Agent, Material) end) end, materials()),
    done = agent(Rounds),
    io:format("Done ~p rounds~n", [Rounds]).

agent(0) ->
    done;
agent(Rounds) ->
    offer(twoRandomMaterials(), Rounds).

offer(AvailableMaterials, Rounds) ->
    receive
        {take, Smoker, AvailableMaterials} ->
            Smoker ! smoke,
            receive
                doneSmoking ->
                    agent(Rounds - 1)
            end
    end.

startSmoker(Agent, Material) ->
    smoker(Agent, lists:delete(Material, materials())).

smoker(Agent, Missing) ->
    Agent ! {take, self(), Missing},
    receive
        smoke ->
            Agent ! doneSmoking,
            smoker(Agent, Missing)
    end.

twoRandomMaterials() ->
    Materials = materials(),
    deleteAt(random:uniform(length(Materials)) - 1, Materials).

materials() ->
    [paper, tobacco, match].

deleteAt(_, []) -> [];
deleteAt(0, [_ | T]) -> T;
deleteAt(Idx, [H | T]) -> [H | deleteAt(Idx - 1, T)].

这里有趣的是,当我们尝试receive {take, Smoker, AvailableMaterials}. 但是,现在只能处理其中之一。然后是内部作为握手。因此,对于一个选择正确的消息,允许代码完成一些工作以及在接收握手消息时不丢失其他消息是解决所有并发问题的方法。如果不匹配/不可处理的消息在任何时候丢弃,s 将永远卡住(除非他们会定期重复他们的请求)。receive doneSmokingtakesmoker

于 2013-02-08T02:04:49.687 回答