在erlang接收语句中引入“保存队列”的目的是什么

时间:2013-02-07 08:51:07

标签: erlang

我是erlang的新手,并从编程Erlang'作者:Joe Armstrong。

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

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

请帮助,我将非常感谢任何证明有用的代码段,谢谢。

3 个答案:

答案 0 :(得分: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(其他)

清空邮箱。

答案 1 :(得分:2)

在构建并发系统时,这是一个巨大的帮助,因为它允许您只关注那些您感兴趣的消息此时并忽略其他消息。 Erlang系统通常是非确定性的,因此您很少知道 。没有自动保存队列,这意味着在您收到消息的每一点上,您都必须能够处理可能到达此过程的每条消息。它很快就会成为状态和信息的组合爆炸。

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

跟踪服务器中某处需要处理的所有消息很快就变得不可行了。例如,在gen_server中,您有客户端发送的实际请求(未指定消息格式),到服务器的“系统”消息(未指定消息格式),对服务器代码有意义的任意数量明确定义的消息除了处理请求所需的所有消息之外。

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

没有消息保存队列会使得编写在处理时发送/接收消息的通用模块几乎不可能,例如客户端功能为gen_server。他们必须知道可以到达该过程并且需要处理的每条消息。

是的,保存所有消息可以成为问题,但通常是您知道的问题类型。例如,在服务器的顶部循环中,您可以合理地确定可以接收和丢弃未知消息。 gen_server确实收到顶层的所有消息,它知道如何处理它们,有些处理它自己(系统消息)以及它在特定服务器代码上传递的其他消息。

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

答案 2 :(得分:2)

+1 to 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}时,队列中可能有三条消息。但是,只有一个可以现在进行处理。然后是内部receive doneSmoking作为握手。因此,对于一个选择适当的消息,允许代码完成一些工作以及在接收握手消息时不丢失其他take消息,这就解决了所有并发问题。如果在任何时候丢失了不匹配/不可处理的消息,smoker将永远被卡住(除非他们会定期重复他们的请求)。