首页 > 解决方案 > 如何在 Erlang 中多次从生成的进程中接收价值?

问题描述

我想通过“接收”从我调用的函数接收多个值。一半的代码如下所述:

-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。(需要注意的是,已通过传递参数 Ev 和 P 调用了 step13 函数)

step13 函数执行以下操作:它将 Ev 的 Pth 索引替换为接收到的元组 {V,V}。

现在,这里是转折点。由于 Parent 中有重复项,我们可以看到两个 1,因此收到的值仅针对 1 之一,因为由于自递归调用 step7(V-1,...),step7 循环已经向前移动。

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 周了,我还是个新手。

标签: processreturnerlangpidspawn

解决方案


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

在每次调用时,您都会生成将发送回消息 {Edgev} 的流程 step13。每个 step13 进程将返回一个且只有一个消息,没有任何条件。

在 step7 中,启动 step13 流程后,您有两个分支:

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

  2. 在下次通话时V == 3,您会收到来自 step13 流程的消息,但您没有阅读它,因此它保留在邮箱中。

  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 条消息。

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

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

[编辑]

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

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

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

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

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

-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).

推荐阅读