3

我是 Erlang 的新手,我正在尝试了解如何将消息从一个进程发送到进程列表。

假设我们有一个数据结构,其中包含一个包含字符串和 Pid 元素的列表。如何让 Pid 向作为前面描述的两个元素之一的 Pid 发送消息“M”?我想出的是:

broadcast(P, M, R) ->
  P ! {self(), friends},
  receive
    {P, Friends} ->
  P ! {self(), {send_message, {M, R, P, Friends}}}
  end.

looper({Name, Friends, Messages}) ->
receive
  {From, friends} ->
    From ! {self(), Friends},
    looper({Name, Friends, Messages});
  {From, {send_message, {M, R, ID, [{FriendPid, FriendName} | FriendTale]}}} ->
    if R =< 0 ->
          From ! {From, {self(), {ID, M}}},
          looper({Name, [{FriendPid, FriendName} | FriendTale], [{ID, M} | Messages]});
       R > 0  andalso FriendTale =/= []->
         FriendPid ! {From, {send_message, {M, R-1, ID, FriendTale}}},
         looper({Name, FriendTale, [{ID, M} | Messages]})
    end;
   terminate ->
    ok
end.

但据我了解,我没有正确匹配 Pid 列表的模式,因此我可以从 Pid 列表的元素中“提取” Pid,或者我没有正确使用列表发送消息到它。

基本上,我有一个名为“looper”的函数,它不断等待新消息的到来。当它收到类型的消息时

{send_message, {M, R, ID, [{FriendPid, FriendName} | FriendTale]}}

其中“M”是我想向名为“Friends”的 Pid 列表广播的消息,R 只是一个整数。

R 基本上是一个整数,表示消息应该走多远。

e.g. 0 = broadcast the message to self,
     1 = broadcast the message to the friends of the pid,
     2 = broadcast the message to the friends of the friends of the pid and so on...

设置 Pid、设置 Pid 之间的“友谊”并广播消息后,我从终端得到的是:

1> f().
ok
2> c(facein).
facein.erl:72: Warning: variable 'From' is unused
{ok,facein}
3> {Message, Pid} = facein:start({"dummy", [], []}).
{ok,<0.186.0>}
4> {Message, Pid2} = facein:start({"dummy2", [], []}).
{ok,<0.188.0>}
5> facein:add_friend(Pid,Pid2).
ok
6> facein:broadcast(Pid,"hello",1).    
=ERROR REPORT==== 5-Oct-2014::12:12:58 ===
Error in process <0.186.0> with exit value: {if_clause,[{facein,looper,1,[{file,"facein.erl"},{line,74}]}]}

{<0.177.0>,{send_message,{"hello",1,#Ref<0.0.0.914>}}}

当我查看广播消息的 Pid 的消息时,控制台只是挂起,其他 Pid 没有收到任何消息。

任何帮助将不胜感激。谢谢

4

2 回答 2

1

I recommend you reduce the complexity of what you are doing to just the essentials first. This conditional processing business in your receive is not part of your basic messaging issue, for example. Here is a basic example of a common broadcast idiom, using a list comprehension to send to a list of pids in the function bcast/2:

-module(bcast).
-export([start/0]).

start() ->
    Pids = [spawn(fun() -> listener() end) || _ <- lists:seq(1,3)],
    Message = "This is my message.",
    ok = bcast(Pids, Message),
    timer:sleep(100), % Give the subordinates time to act.
    [exit(P, kill) || P <- Pids],
    ok.

listener() ->
    receive
        {bcast, Message} ->
            Now = now(),
            io:format(user, "~p ~p: Received: ~p~n", [self(), now(), Message]),
            listener();
        Any ->
            io:format(user, "~p HURR! Unexpected message: ~p~n", [self(), Any]),
            listener()
    end.

bcast(Pids, Message) ->
    BCast = fun(Pid) -> send(Pid, {bcast, Message}) end,
    lists:foreach(BCast, Pids).

The other issues you are having in your code aren't really broadcast issues, they are problems with getting ahead of yourself in an unfamiliar language.

This example is asynchronous (we're only sending a message one way) and requires we kill our subordinate processes because I've only written an infinite loop. Those aspects are the first things to play with: how to deal with your message queue (as in, the whole mailbox) not just send a message, receive a message in a loop; and think about how you want your subordinate processes to die when things go right (I just murder them all in the example above).

EDIT It is good to know that there is another, somewhat common, way of writing the bcast/2 function above using a list comprehension that is assigned to the "I don't care" variable _:

bcast(Pids, Message) ->
    _ = [P ! {bcast, Message} || P <- Pids],
    ok.

I used to prefer this list comprehension style because it was the first style I was introduced to -- but over the last several years I've become a big fan of the semantic correctness of lists:foreach/2. The point is to not get confused when you see a list comprehension -- it is an important expression type to be able to read!

于 2014-10-05T13:33:20.767 回答
1

错误信息

这次你得到的是if_clause错误。在 Erlang 中,每个表达式都必须返回一些值,if包括在内。这意味着您可以编写这样的代码

SomeVar = if 
     R =< 0 ->
       [...]
     R > 0 andalso FriendTale =/= []->
       [...]
  end

正如您所看到的,是否需要“返回”某些东西,并且要做到这一点,它的一个分支需要运行。或者换句话说,它的一个条款需要马赫。但在你的情况下,他们什么时候R > 0FriendsTale =:= []没有。因此出现运行时错误。

作为一般实用的最后一个子句保留为

  _ ->  
     [...]

它将始终匹配,并使您免于此类错误。

在您的示例中,您根本不必使用if。你可以做的是用一些守卫来扩展你的接收条款

looper({Name, Friends, Messages}) ->
  receive
    {From, {send_message, {M, R, ID, [{FriendPid, FriendName} | FriendTale]}}} 
       when R =< 0 ->
          From ! {From, {self(), {ID, M}}},
          looper({Name, [{FriendPid, FriendName} | FriendTale], [{ID, M} | Messages]});
    {From, {send_message, {M, R, ID, [{FriendPid, FriendName} | FriendTale]}}}  
       when R > 0  andalso FriendTale =/= [] ->
          FriendPid ! {From, {send_message, {M, R-1, ID, FriendTale}}},
          looper({Name, FriendTale, [{ID, M} | Messages]});
    terminate ->
          ok
  end.

如果receive收到的消息不必匹配一个子句。如果没有,它只是留在消息框中(在此接收中被忽略,但可能被另一个捕获)。

或者放纵你的逻辑,你可以对R自己进行模式匹配

looper({Name, Friends, Messages}) ->
  receive
    {From, {send_message, {M, 0, ID, [{FriendPid, FriendName} | FriendTale]}}} ->
          From ! {From, {self(), {ID, M}}},
          looper({Name, [{FriendPid, FriendName} | FriendTale], [{ID, M} | Messages]});
    {From, {send_message, {M, 1, ID, [{FriendPid, FriendName} | FriendTale]}}}  
       when FriendTale =/= [] ->
          FriendPid ! {From, {send_message, {M, R-1, ID, FriendTale}}},
          looper({Name, FriendTale, [{ID, M} | Messages]});
    terminate ->
          ok
  end.

为了提高就绪性,您可以R从 opque integer 更改为 Miningfull atom

looper({Name, Friends, Messages}) ->
  receive
    {From, {send_message, {M, back_to_me, ID, [{FriendPid, FriendName} | FriendTale]}}} ->
          From ! {From, {self(), {ID, M}}},
          looper({Name, [{FriendPid, FriendName} | FriendTale], [{ID, M} | Messages]});

    {From, {send_message, {M, to_friends, ID, [{FriendPid, FriendName} | FriendTale]}}}  
       when FriendTale =/= [] ->
          FriendPid ! {From, {send_message, {M, R-1, ID, FriendTale}}},
          looper({Name, FriendTale, [{ID, M} | Messages]});

    terminate ->
          ok
  end.

广播给朋友

如果我理解正确looper是代表一个“人”的功能。每个朋友都是存储朋友列表的进程,可以添加和删除,还可以向其他朋友发送消息。

让我们从为每个函数创建子句开始(创建流程接口)

looper(Name, Friends, Messages) ->
  receive 
     {add_friend, Friend} ->
        [...];
     {remove_friend, Friend} ->
        [...];
     {receive_message, Message} ->
        [...];frineds
     {broadcast_to_self, Message} ->
        [...];
     {broadcast_to_friends, Message} ->
        [...];
     terminate ->
        ok
  end

其中大多数都很容易实现,例如

{add_frined, Friend} ->
    looper(Name, [Friend, Friends], Messages);

所以我不会详细说明。

进行广播的那些不会改变状态,所以现在让我们写这样的东西(主要是为了可读性

     {broadcast_to_friends, Message} ->
        handle_broadcast_to_friends(Friends, Message),
        looper(Name, Friends, Messages);

并在下面实现新功能

handle_broadcast_to_friends(Friends, Message) ->
   [ F ! {receive_message, Message} || F <- Friends ].

现在,由于确切地知道要发送哪个原子的元组并不方便,我们可以将我们的“消息接口”包装到“函数接口”中。例如

add_friend(Person,  Friend) ->
   Person ! {add_friend, Friends}.

receive_message(Person, Message) ->
    Person ! {receive_message, Message}.

我们也可以在您的逻辑实现中使用它们

handle_broadcast_to_friends(Friends, Message) ->
   [ receive_message(F, Message)  || F <- Friends ].

那应该让你走上正轨。如果您需要MessageID或类似的东西,只需扩展您的界面。如果你真的需要创建broadcast_to_all,你需要考虑如何处理循环的消息,这不是一个简单的问题。

于 2014-10-05T13:00:29.053 回答