Search code examples
processreturnerlangpidspawn

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


I want to receive multiple values through 'receive' from a function that I called. Half of the code is as stated below:

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

(Apologies for such a problem-oriented code instead of a generic one, the cleaning of the code was messing up one or the other things, I am explaining the code below)

Explanation:

So, initially, 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 function computes P as Vth element of Parent and calls step13 function, sends a tuple {V,V} to function step13 whenever V is not equal to R.(to be noted, step13 function was called by passing parameters Ev and P already)

step13 function does the following: It replaces the Pth index of Ev with received tuple {V,V}.

Now, here is the twist. Since there are duplicates in Parent, as we can see two 1, the value received is only for one of the 1, as step7 loop has already moved forward due to self recursive call 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}]]

Order might change, order is not necessary for me.

So, basically, I want to spawn, and from that spawned processes, I want to receive all the computed values in the original calling function.

I doubt how much clear have I been, Please ask me in comments, I will clarify the doubts. Thanks a lot, It has been some 1 week of me learning Erlang, I am still a newbie.


Solution

  • step7 is recursively called 5 times, with parameter V equal to 4,3,2,1,0.

    at each call, you spawn the process step13 which will send back a message {Edgev}. Each step13 process will send back one and only one message, without any condition.

    in step7, after the launch of the step13 process, you have two branches:

    • if V is in parent (it will happen only when V is 4 or 1) you go in a receive block to wait for a message.
    • else you do nothing
    • List item
    1. At the first call, V == 4, you receive the message from the step13 process, read it and remove it from the mailbox

    2. at next call, V == 3, you receive the message from the step13 process but you don't read it so it stays in the mailbox.

    3. Same thing at next call with V == 2

    4. Next, V == 1, you receive the message from the step13 process, read the mailbox and you get the message which was sent when V was equal to 3. the messages sent for V = 2 and 1 are still in the mailbox.

    5. Finally, at the next call, V == 0 and your program exits, with the mailbox still full.

    I see the following problems in your code

    The condition (lists:member(V,Parent) == true) is true only for V ==4 and V ==1. You can receive only 2 messages, but you expect 3.

    As you put the receive bloc under condition, you leave unwanted messages in the mailbox which will be read later in place of the expected ones (I don't understand what you are trying to do, but it is my guess) You should first receive the message and then perform the test, or, add a reference to the call, send it back in the return message and conditionally receive the message with this reference (in my opinion a bad solution since it does not empty the mailbox).

    Step7 launches a process and wait for its answer. Unless the real code should process other data in between, it is useless. a simple call would be much more efficient.

    [EDIT]

    With your comment, and reading your code I made a first assumption that

    • You want to process the function step13 only when V = 4,3,2
    • collect all the results

    If this is correct, I made a first modification in your code where step7 has 3 different steps

    1. spawn all step13 processes
    2. collect all responses
    3. return the result

    This is the logical order, but as often, the steps appear in reverse order in the code:

    -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,[]).
    

    Or this version which looks cleaner to me

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