1
votes

I wrote a simple program in Erlang. Its task is to compute the value of given polynomial 2*x^2 + 3*x + 5. Both 2*x^2 and 3*x are computed parallely, and at the very end these both parts are added together, 5 is added and finally we get the result. There is one function computing 2*x^2, the second one computing 3*x and the last one which calculates the final result. Here is the code:

-module(count2).
-compile([export_all]).
%-export([f_main/0]).

%computes the value of the polynomial 2*x^2 + 3*x + 5

f_result() ->
    {Arg1, Res_1} = receive
        {f1, X1, Temp1} -> {X1, Temp1}
    end,
    Res_2 = receive
        {f2, _, Temp2} -> Temp2
    end,
    Res = Res_1 + Res_2 + 5, %adds 5 to the result of 2*X^2+3*X
    io:format("f(~p) = ~p~n",[Arg1, Res]),
    f_result().

%when the line 14 was io:format("f(~p) = ~p~p,[X1, Res], the compilation error occured:
%variable 'X1' unsafe in 'receive'


f_2(PidWyn) -> %computes 2*X^2
    receive
        {f2, X} ->
            Res = 2*math:pow(X,2),
            PidWyn ! {f2, X, Res},
            f_2(PidWyn);
        {finish} ->
            io:format("f_2 : finish~n")
end.

f_1(PidWyn) -> %computes 3*X
    receive
        {f1, X} ->
            Res = 3*X,
            PidWyn ! {f1, X, Res},
            f_1(PidWyn);
        {finish} ->
            io:format("f_1 : finish~n")
end.        

f_main() ->
    PidW = spawn(?MODULE, f_result, []), %the process ID of the function that computes and displays the final result
    Pid1 = spawn(?MODULE, f_1, [PidW]),
    Pid2 = spawn(?MODULE, f_2, [PidW]),
    L = [1,2,3,4,5],
    [Pid1 ! {f1, X} || X <- L], %sends a series of messages to the function f_1
    [Pid2 ! {f2, X} || X <- L], %sends a series of messages to the function f_2
    Pid2 ! {finish},
    Pid1 ! {finish},
    PidW ! {finish}, %sends the message to the function f_result to make it stop
    io:format("f_main : finish~n").

As you can see, the f_result function has two receive statements one by one. The first one receives the tuple from f_1 function, and the second one receives the tuple from f_2 function. After that the function displays the results and calls itself to make a loop.

I would like to make f_result function receive {finish} tuple what would tell it to stop working (similar solution is visible in f_1 and f_2 function), but I can't place required receive properly. I tried to put

{Arg1, Res_1} = receive
                 {f1, X1, Temp1} -> {X1, Temp1};
                 {finish} -> io:format("f_result : finish~n"),
                   exit(0)
               end,

but it makes the following output:

f_main : finish
f_result : finish
f_1 : finish
f_2 : finish
ok

I will be grateful if you can give me some advice.

2

2 Answers

1
votes

There's not necessarily anything wrong with that output. In Erlang, messages are sent asynchronously, so it is possible for the process running f_result to process the message earlier than the processes running f_1 and f_2.

As an aside, instead of exit(0), you'd usually write exit(normal) to signal that the process exits "normally", not because of an error. This convention is relied upon in a few places. For example, a process with a non-normal exit causes linked processes to exit as well, and with the transient supervisor strategy, you can make the supervisor restart processes if and only if their exit reason is not normal.

0
votes

Although the output you see doesn't necessarily mean there is something wrong with your code it actually is. All messages in Erlang are asynchronous and keeps only one promise. Two messages sent from one process to the other will arrive in the same order if they ever arrive.

First why the output doesn't tell you anything. IO operations of the io module as io:format/1,2,3 are performed using messages. It means you send four messages from four processes to the io server process. (In your case to the group leader which is by default named process user performing standard IO operations.) It is not guaranteed that those messages arrive in the same order as you expect. The processes involved make four independent pairs of processes. They usually arrive in the same order, but you can't rely on it.

Second you have a similar issue with your finish message from f_main process to the f_result. The message could and probably would arrive in the f_result process before results from f_1 and f_2. It will cause your first receive in proposed solution will finish your f_result prematurely.

The solution is force finish message arrive in proper order with input. It can be done only if it travels via the same path, same pairs of processes. You have to send finish to the f_result via f_1. So you could do it in this way:

-module(count2).
-compile([export_all]).
%-export([f_main/0]).

%computes the value of the polynomial 2*x^2 + 3*x + 5

f_result() ->
    {Arg1, Res_1} = receive
                        {f1, X, Temp1} -> {X, Temp1};
                        finish ->
                            io:format("f_result : finish~n"),
                            exit(normal)
                    end,
    Res_2 = receive
                {f2, _, Temp2} -> Temp2
            end,
    Res = Res_1 + Res_2 + 5, %adds 5 to the result of 2*X^2+3*X
    io:format("f(~p) = ~p~n",[Arg1, Res]),
    f_result().

f_2(PidWyn) -> %computes 2*X^2
    receive
        {f2, X} ->
            Res = 2*X*X,
            PidWyn ! {f2, X, Res},
            f_2(PidWyn);
        finish ->
            io:format("f_2 : finish~n")
    end.

f_1(PidWyn) -> %computes 3*X
    receive
        {f1, X} ->
            Res = 3*X,
            PidWyn ! {f1, X, Res},
            f_1(PidWyn);
        finish ->
            PidWyn ! finish,
            io:format("f_1 : finish~n")
    end.        

f_main() ->
    PidW = spawn(?MODULE, f_result, []), %the process ID of the function that computes and displays the final result
    Pid1 = spawn(?MODULE, f_1, [PidW]),
    Pid2 = spawn(?MODULE, f_2, [PidW]),
    L = [1,2,3,4,5],
    [Pid1 ! {f1, X} || X <- L], %sends a series of messages to the function f_1
    [Pid2 ! {f2, X} || X <- L], %sends a series of messages to the function f_2
    Pid2 ! finish,
    Pid1 ! finish,
    io:format("f_main : finish~n").

And result:

> count2:f_main().
f_main : finish
f_1 : finish
f_2 : finish
f(1) = 10
f(2) = 19
ok
f(3) = 32
f(4) = 49
f(5) = 70
f_result : finish

Note that only f(X) = Y and f_result : finish are guaranteed to appear in this order because they are messages from the same f_result process to the user io server. (Even f_main : finish and ok could appear on reverse order due to the way how shell works internally, but it is unnecessary to know this detail. You will probably never see this happen.)