Search code examples
erlangconcurrent-programming

Erlang Code understanding


Hi I am doing some Erlang training by understanding some basic Erlang server modules. However I am getting stuck on this one. In this module I am supposed to tell the lowest and highest possible values that can be printed by each of the 3 processes (parent and 2 child processes) because depending on particular order of execution, different values may get printed by the 3 processes.

I executed the test and got 2 for Parent, 3 for both of the child processes but I don't know how they got those values. Can someone specifically explain the process to me? Appreciated.

Here is the module:

-module(p4).
-export([start/0, init/0, read/1, incr/1, reset/1, test/0]).

start()->
    spawn(fun() -> init() end).
init() -> loop(0).

loop(N) ->
    receive
   {read, Pid} ->
	    Pid ! {value, self(), N},
	    loop(N);
	{incr, Pid} ->
            Pid ! {incr_reply, self()},
	    loop(N+1);
	{reset, Pid} ->
            Pid ! {reset_reply, self()},    
	    loop(0)
   end.

read(Serv) ->
    Serv ! {read, self()},
    receive {value, Serv, N} -> N end.

incr(Serv)->
    Serv ! {incr, self()},
    receive{incr_reply, Serv} -> ok end.

reset(Serv) ->
    Serv ! {reset, self()},
    receive {reset_reply, Serv} -> ok end.
	    
test() -> 	  
    Server = start(),
    spawn(fun() -> incr(Server),
		   io:format("Child 1 read ~p~n", [read(Server)]) end),
    incr(Server),
    spawn(fun() -> incr(Server),
	           io:format("child 2 read ~p~n", [read(Server)]) end),
    io:format("Parent read ~p~n", [read(Server)]).
		  


Solution

  • Try to print any message server is receiving (or use debugger tracing) for better understanding and you should get something like:

    Server state was: 0 and received: {incr,<0.59.0>}
    Server state was: 1 and received: {incr,<0.109.0>}
    Server state was: 2 and received: {read,<0.59.0>}
    Server state was: 2 and received: {incr,<0.110.0>}
    Parent read 2
    Server state was: 3 and received: {read,<0.109.0>}
    Server state was: 3 and received: {read,<0.110.0>}
    Child 1 read 3
    child 2 read 3
    

    <0.59.0> is parent process, <0.109.0> is child 1 and <0.110.0> is child 2.

    It means that parent read message was delivered before read of children processes but after first child incr. But it doesn't have to be like this. It depends on process scheduling. The only guarantee you have is that messages sent from process A to process B will be delivered in the same order. Due to synchronous nature of incr(Server) and read(Server) it doesn't really matter. Because each of these processes runs incr(Server) before read(Server) it must get at least 1 but note that child 2 was spawned after parent executed incr(Server) which is synchronous operation so it must be at least 1 when it runs its own incr(Server), so when it reads it must be at least 2. The maximum value for each of them is 3 (overall number of incr(Server) and means that any read(Server) of each of them may be delayed).

    Summary of possible printed values: Parent: 1,2,3; Child 1: 1,2,3; Child 2: 2,3

    Simplified execution order of:

    your case (parent gets 2 and both children get 3):

    parent: spawn server
    parent: spawn child 1
    parent: incr(Server)
    child 1: incr(Server)
    parent: spawn child 2
    parent: io:format("parent read ~p~n",[read(Server)]) % prints 2
    child 2: incr(Server)
    child 1: parent: io:format("child 1 read ~p~n",[read(Server)]) % prints 3
    child 2: parent: io:format("child 2 read ~p~n",[read(Server)]) % prints 3
    

    minimum case for child 1:

    parent: spawn server
    parent: spawn child 1
    child 1: incr(Server)
    child 1: io:format("child 1 read ~p~n",[read(Server)]) % prints 1
    ...
    

    maximum case for parent:

    parent: spawn server
    parent: spawn child 1
    parent: incr(Server)
    parent: spawned child 2
    child 1: incr(Server)
    child 2: incr(Server)
    Parent: io:format("child 1 read ~p~n",[read(Server)]) % prints 3
    ...
    

    I've created massive test which spawned 100000 processes which run test/0 simultaneously and created external stat_server which gets and counts every process read(Server) resault and here it is:

    [{{child1,1},2}, % child 1 reads 1 only twice. Sometimes it's 1 sometimes it's 0, it varies
     {{child1,2},53629},
     {{child1,3},46369},
     {{child2,2},107},
     {{child2,3},99893},
     {{parent,1},855},
     {{parent,2},99112},
     {{parent,3},33}]