如何在 Erlang 中多次从生成的进程中接收值?

How can I receive value from spawned process multiple times in Erlang?

我想通过 'receive' 从我调用的函数接收多个值。一半代码如下:

-module(b).
-export([step13/3,step7/4,run/0]).

step13(P,Ev,Pid) ->
   Lst = lists:nth(P,Ev),
    receive
        {E} ->
            List = Lst ++ [E], L = lists:usort(List)
   end,
   Edgev = lists:sublist(Ev,P-1) ++ [L] ++ lists:nthtail(P,Ev),
   Pid ! {Edgev}.
   

step7(0,_,_,_) ->
    io:fwrite( "Step7 done");

step7(V,R,Ev,Parent) ->
    case (V == R) of
        true -> io:fwrite( "Root vertex, so leaving ~n");
        false -> E = {V,V},
           io:fwrite( "For the vertex is ~w  ~n", [V] ),
           io:fwrite( "New edge is ~w  ~n", [E] ),
           P = lists:nth(V,Parent),
           Pid = spawn(b,step13,[P,Ev,self()]),
           Pid ! {E}
    end,
    case (lists:member(V,Parent) == true) of
        true -> 
            receive
              {Edgev} ->
                io:fwrite( "Ev now is ~w  ~n", [Edgev] )
            end;
        false -> io:fwrite( "" )
    end,
    step7(V-1,R,Ev,Parent).

run() ->
    V = 4,
    Ev = [[{1,2},{1,3},{1,4}],[{2,1},{2,3}],[{3,1},{3,2},{3,4}],[{4,1},{4,3}]] ,
    R = 1,
    Parent = [0,1,4,1],
    step7(V,R,Ev,Parent).

(对于这种面向问题的代码而不是通用代码表示歉意,代码的清理弄乱了一个或另一个东西,我在下面解释代码)

解释:

所以,最初, Ev is [[{1,2},{1,3},{1,4}],[{2,1},{2,3}],[{3,1},{3,2},{3,4}],[{4,1},{4,3}]] , R is 1(always fixed), V is 4, Parent is [0,1,4,1]

step7函数将P计算为Parent的第V个元素并调用step13函数,每当V不等于R时,将元组{V,V}发送给函数step13。(需要注意的是,step13函数是通过传递参数调用的Ev 和 P 已经)

step13 函数执行以下操作:它用接收到的元组 {V,V} 替换 Ev 的第 P 个索引。

现在,转折点来了。由于Parent中有重复项,我们可以看到两个1,收到的值只是1中的一个,因为step7循环由于自递归调用step7(V-1,...).

What output I am getting is:

Ev now is [[{1,2},{1,3},{1,4},{2,2}],[{2,1},{2,3}],[{3,1},{3,2},{3,4}],[{4,1},{4,3}]] 
Ev now is [[{1,2},{1,3},{1,4},{4,4}],[{2,1},{2,3}],[{3,1},{3,2},{3,4}],[{4,1},{4,3}]] 


But what I want is:

Ev now is [[{1,2},{1,3},{1,4},{4,4}],[{2,1},{2,3}],[{3,1},{3,2},{3,4}],[{4,1},{4,3}]] 
Ev now is [[{1,2},{1,3},{1,4}],[{2,1},{2,3}],[{3,1},{3,2},{3,4}],[{4,1},{4,3}]]
Ev now is [[{1,2},{1,3},{1,4},{2,2}],[{2,1},{2,3}],[{3,1},{3,2},{3,4}],[{4,1},{4,3}]]

顺序可能会改变,我不需要顺序。

所以,基本上,我想生成,并从生成的进程中接收原始调用函数中的所有计算值。

我怀疑自己到底讲清楚了多少,欢迎评论问我,我会解惑的。非常感谢,我学习Erlang已经有1个星期了,我还是个新手。

step7递归调用5次,参数V等于4,3,2,1,0。

在每次调用时,您都会生成进程 step13,它将发回一条消息 {Edgev}。每个 step13 进程将不带任何条件地发回一条且仅一条消息。

在step7中,step13进程启动后,你有两个分支:

  • 如果 V 在父级中(只有当 V 为 4 或 1 时才会发生),您将进入接收块以等待消息。
  • 否则你什么都不做
  • 列表项
  1. 在第一次调用时,V == 4,您收到来自 step13 进程的消息,读取它并将其从邮箱中删除

  2. 在下次调用时,V == 3,您收到来自步骤 13 进程的消息,但您没有阅读它,因此它留在邮箱中。

  3. 下次与 V == 2

    通话时相同
  4. 接下来,V == 1,你收到来自step13进程的消息,读取邮箱,你得到了V等于3时发送的消息。为V发送的消息= 2 和 1 仍在邮箱中。

  5. 最后,在下一次调用时,V == 0 程序退出,邮箱仍然是满的。

我在你的代码中发现了以下问题

条件 (lists:member(V,Parent) == true) 仅对 V ==4 和 V ==1 为真。您只能收到 2 条消息,但您希望收到 3 条消息。

当您将接收块置于条件下时,您在邮箱中留下了不需要的消息,这些消息将在以后读取以代替预期的消息(我不明白你想做什么,但这是我的猜测) 您应该先接收消息然后执行测试,或者,添加对调用的引用,将其发送回 return 消息并有条件地接收带有此引用的消息(我认为这是一个糟糕的解决方案,因为它不会清空邮箱)。

Step7 启动一个进程并等待它的响应。除非真正的代码应该处理其间的其他数据,否则它是没有用的。一个简单的调用会更有效率。

[编辑]

根据您的评论和阅读您的代码,我做出了第一个假设

  • 你只想在V = 4,3,2时处理函数step13
  • 收集所有结果

如果这是正确的,我对您的代码进行了第一次修改,其中 step7 有 3 个不同的步骤

  1. 生成所有 step13 进程
  2. 收集所有回复
  3. return结果

这是逻辑顺序,但代码中的步骤通常以相反的顺序出现:

-module(b).
-export([step13/3,step7/6,run/0]).

step13(P,Ev,Pid) ->
   Lst = lists:nth(P,Ev),
    receive
        {E} ->
            List = Lst ++ [E], L = lists:usort(List)
   end,
   Edgev = lists:sublist(Ev,P-1) ++ [L] ++ lists:nthtail(P,Ev),
   Pid ! Edgev.
   

% step7(CurrentVertex,RootVertex,Edges,Parent,NumberOfResponsesExpected,Response)

% all vertices has been processed, all answers received
step7(0,_,_,_,0,Resp) ->
    Resp;

% all vertices has been processed, waiting for answers
step7(0,_,_,_,NbResp,Resp) ->
    receive
        Edgev -> step7(0,0,0,0,NbResp-1,[Edgev|Resp])
    end;

% remaining vertices to proceed 
step7(V,R,Ev,Parent,NbResp,Resp) ->
    NewNbResp = case (V == R) of
        true -> NbResp;
        false -> E = {V,V},
           P = lists:nth(V,Parent),
           Pid = spawn(b,step13,[P,Ev,self()]),
           Pid ! {E},
           NbResp + 1
    end,
    step7(V-1,R,Ev,Parent,NewNbResp,Resp).

run() ->
    V = 4,
    Ev = [[{1,2},{1,3},{1,4}],[{2,1},{2,3}],[{3,1},{3,2},{3,4}],[{4,1},{4,3}]] ,
    R = 1,
    Parent = [0,1,4,1],
    step7(V,R,Ev,Parent,0,[]).

或者我觉得这个版本更干净

-module(b).
-export([run/0]).

step13(P,Ev,Pid,E) ->
  % the usage of a spawned process makes sense if the real algorithm may take time
  List = lists:usort(lists:nth(P,Ev) ++ [E]),
  Pid ! {self(),lists:sublist(Ev,P-1) ++ [List] ++ lists:nthtail(P,Ev)}.
   
% spawn processes
step7(V,R,Ev,Parent) ->
  Me = self(),
  F = fun(X) -> spawn(fun() -> step13(lists:nth(X,Parent),Ev,Me,{X,X}) end) end,
  Pids = [F(X) || X <- lists:seq(1,V), X =/= R],
  % another case were spawning processes may be useful is if you can have significant code here
  receiveAnswers(Pids,[]).

% receive answers
receiveAnswers([],R) -> R;
receiveAnswers([H|T],R) ->
  receive
    % H is one of the Pids of the step13 processes.
    % Use it to guarantee the message comes from an expected process
    {H,Edgev} -> receiveAnswers(T,[Edgev|R])
  end.

run() ->
  V = 4,
  Ev = [[{1,2},{1,3},{1,4}],[{2,1},{2,3}],[{3,1},{3,2},{3,4}],[{4,1},{4,3}]] ,
  R = 1,
  Parent = [0,1,4,1],
  step7(V,R,Ev,Parent).