【问题标题】:What's the purpose to introduce 'save queue' in erlang receive statement在 erlang 接收语句中引入“保存队列”的目的是什么
【发布时间】:2013-01-22 17:05:07
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: erlang


    【解决方案1】:

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

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

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

    一种用法是管理优先级:

    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(其他)

    清空邮箱。

    【讨论】:

    • match on 更改为match one
    【解决方案2】:

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

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

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

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

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

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

    它可以让您轻松处理@Pascal 显示的优先级消息。

    【讨论】:

    • 非常感谢,对理解这个设计很有帮助。
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-14
      • 1970-01-01
      • 2010-11-24
      • 2012-03-15
      • 2022-01-09
      • 1970-01-01
      • 1970-01-01
      • 2016-03-31
      相关资源
      最近更新 更多