Search code examples
processerlangdistributed-computingspawnerlang-otp

How can I get return value from a spawned process in Erlang?


I have the following code:

-module(a).
-compile(export_all).

say(2,0) ->
    [1,2];

say(A,B) ->
    say(A-1,B-1).

loop(0) ->
    io:format("");

loop(Times) ->
    L = spawn(a, say, [4,2]),
    io:fwrite( "L is ~w  ~n", [L] ),
    loop(Times-1).

run() ->
    loop(4).

I want to have the list [1,2] in L each time function 'say' completes. However, since the pid of the process is returned instead of the list from the function say due to use of spawn, I am getting the following output:

L is <0.113.0>  
L is <0.114.0>  
L is <0.115.0>  
L is <0.116.0>  

What I desire is

L is [1,2]
L is [1,2]
L is [1,2]
L is [1,2]

How can I achieve this?


Solution

  • To pass information between processes, you use ! to send a message to another process's mailbox, and you use a receive clause to extract a message from a process mailbox. Here is an example:

    -module(a).
    -compile(export_all).
    
    %% Worker process:
    say(From, 2, 0) ->
        From ! {self(), [1,2]};
    say(From, A, B) ->
        say(From, A-1, B-1).
    
    
    %%  Main process:
    loop(0) ->
        ok;
    loop(Times) ->
        Pid = spawn(a, say, [self(), 4, 2]),
        receive  %%waits here for result before spawning another process--no concurrency
            {Pid, Result} ->
                io:fwrite( "L is ~w  ~n", [Result] )
        end,
        loop(Times-1).
    
    
    %%  Test:
    run() ->
        loop(4).
    

    In the shell:

    7> c(a).   
    a.erl:2: Warning: export_all flag enabled - all functions will be exported
    {ok,a}
    
    8> a:run().
    L is [1,2]  
    L is [1,2]  
    L is [1,2]  
    L is [1,2]  
    ok
    
    9> 
    

    Or, you can spawn all the processes, then read the results as they come in:

    -module(a).
    -compile(export_all).
    
    %% Worker process:
    say(From, 2, 0) ->
        From ! [1,2];
    say(From, A, B) ->
        say(From, A-1, B-1).
    
    
    %%  Main process:
    loop(N) ->
        loop(N, N).
    
    loop(0, Times) ->
        display_results(Times);
    loop(N, Times) ->
        spawn(a, say, [self(), 4, 2]),
        loop(N-1, Times).
     
    display_results(0) -> 
        ok;
    display_results(Times) ->
        receive
            Result ->
                io:format("L is ~w~n", [Result])
        end,
        display_results(Times-1).
    
    %%  Test:
    run() ->
        loop(4).
    

    To ensure that you only receive messages from the processes that you spawned, you can do this:

    -module(a).
    -compile(export_all).
    
    %% Worker process:
    say(From, 2, 0) ->
        From ! {self(), [1,2]};
    say(From, A, B) ->
        say(From, A-1, B-1).
    
    
    %%  Main process:
    loop(Times) ->
        loop(Times, _Pids=[]).
    
    loop(0, Pids) ->
        display_results(Pids);
    loop(Times, Pids) ->
        Pid = spawn(a, say, [self(), 4, 2]),
        loop(Times-1, [Pid|Pids]).
    
    
    display_results([]) -> 
        ok;
    display_results([Pid|Pids]) ->
        receive
            {Pid, Result} ->
                io:format("L is ~w~n", [Result])
        end,
        display_results(Pids).
    
    %%  Test:
    run() ->
        loop(4).
    

    There are some risks when using a receive like that: if a worker process crashes before it sends the message to your main process, then your main process will be stuck indefinitely in the receive while waiting for a message to arrive from the crashed process. One solution: use a timeout in the receive. Another: use spawn_monitor().