以圈子形式发送消息。

时间:2011-07-29 21:34:57

标签: concurrency erlang

我是函数式编程的新手,只是从haskell(不喜欢它)切换到erlang(非常喜欢它)。当我作为一个自学者学习时,我偶然发现了these Exercises并开始做它们。

我遇到了这个问题:

  
      
  1. 编写一个启动2个进程的函数,并发送消息M.   在他们之间向前和向后的时间。消息发布后   已被发送的流程应该优雅地终止。
  2.   

我这样解决它并且它有效(也许它可以做得更好;任何评论都高度赞赏):

-module (concur).
-export ( [pingpong/1, pingpong/2] ).

pingpong (Msg, TTL) ->
    A = spawn (concur, pingpong, ["Alice"] ),
    B = spawn (concur, pingpong, ["Bob"] ),
    B ! {A, TTL * 2, Msg}.

pingpong (Name) ->
    receive
        {From, 1, Msg} -> 
            io:format ("~s received ~p and dying.~n", [Name, Msg] ),
            exit (From);
        {From, TTL, Msg} ->
            io:format ("~s received ~p.~n", [Name, Msg] ),
            From ! {self (), TTL - 1, Msg},
            pingpong (Name)
    end.

真正的问题是下一个练习:

  

2)编写一个在环中启动N个进程的函数,并发送一个   消息M次围绕环中的所有进程。之后   消息已经发送,进程应该优雅地终止。

由于我没有将消息发送回其创建者,而是发送到链中的下一个节点,因此我不得不将接收者的过程传递给发送过程。所以我想象这个函数看起来像这样:

pingCircle (Name, Next) ->
...
    receive {TTL, Msg} -> Next ! {TTL - 1, Msg}
...

但我如何开始这整个事情。当我在圆圈中生成第一个函数时,我仍然没有生成下一个节点,因此我无法将其作为参数传递。所以我的天真方法不起作用:

First = spawn (concur, pingCirle, ["Alice", Second] ),
Second = spawn (concur, pingCirle, ["Bob", Third] ),
...

另外,将下一个节点的spawn调用作为参数递归传递给它的前一个方法,并没有解决如何关闭圆圈的问题,即将最后一个节点传递给第一个节点。

问题是: 我该如何建立这个圈子呢?

修改

感谢您的出色答案,我成功实现了我的目标。因此这个问题就解决了。

一种可能的解决方案是:

-module (concur).
-export ( [pingCircle/3, pingCircle/2] ).

pingCircle (Names, Message, TTL) ->
    Processes = lists:map (fun (Name) -> spawn (?MODULE, pingCircle, [Name, nobody] ) end, Names),
    ProcessPairs = lists:zip (Processes, rot1 (Processes) ),
    lists:map (fun ( {Process, Recipient} ) -> Process ! {setRecipient, Recipient} end, ProcessPairs),
    Circle = lists:map (fun ( {Process, _} ) -> Process end, ProcessPairs),
    hd (Circle) ! {Message, TTL - 1, lists:last (Circle) }.

rot1 ( [] ) -> [];
rot1 ( [Head | Tail] ) -> Tail ++ [Head].

pingCircle (Name, Recipient) ->
    receive
        {setRecipient, NewRecipient} ->
            pingCircle (Name, NewRecipient);
        {Message, 0, Originator} ->
            io:format ("~s received ~p with TTL 0 and dying.~n", [Name, Message] ),
            if
                Originator == self () -> io:format ("All dead.~n");
                true -> Recipient ! {Message, 0, Originator}
            end;
        {Message, TTL, Originator} ->
            io:format ("~s received ~p with TTL ~p.~n", [Name, Message, TTL] ),
            if
                Originator == self () -> Recipient ! {Message, TTL - 1, Originator};
                true -> Recipient ! {Message, TTL, Originator}
            end,
            pingCircle (Name, Recipient)
    end.

这是我的peer review link

4 个答案:

答案 0 :(得分:3)

这项练习已经成为所有erlang程序员的成年礼。我为它提供了一个有效的解决方案here,以及可能有用的解释。

答案 1 :(得分:1)

首先生成它们,然后向它们发送一个开始信号。

在所有进程都已运行后,将发送启动信号。

答案 2 :(得分:1)

有人已经在这里提出了答案 - > http://simplehappy.iteye.com/?show_full=true

答案 3 :(得分:1)

我的回答。

-module(con_test).

start_ring(Msg, M, N) ->
    [First|_]=Processes=[spawn(?MODULE, ring, []) || _ <- lists:seq(1,N)],
    First ! {rotLeft(Processes), {Msg, M*N}}.

ring() ->
    receive
        {_List, {Msg, Count}} when Count == 0 ->
            io:format("~p got ~s. Enough! I'm out.~n", [self(), Msg]),
            exit(normal);
        {[Next|_] = List, {Msg, Count}} when Count > 0 ->
            io:format("~p got ~s. Passing it forward to ~p.~n", [self(), Msg, Next]),
            Next ! {rotLeft(List), {Msg, Count-1}},
            ring()
    after 1000 ->
        io:format("~p is out.~n", [self()]),
        exit(normal)
    end.

rotLeft([])     ->  [];
rotLeft([H|T])  ->  T ++[H].