I'm trying to understand request order in erlang, but I can't seem to grasp it very well. Here's the example code:
test() ->
Server = start(),
spawn(fun() -> write(Server, 2),
io:format(”Child 1 read ~p~n”, [read(Server)]) end),
write(Server, 3),
write2(Server, 5),
spawn(fun() -> write2(Server, 1),
io:format(”Child 2 read ~p~n”, [read(Server)]) end),
io:format(”Parent read ~p~n”, [read(Server)]).
And here's the server:
start() ->
spawn(fun() -> init() end).
init() -> loop(0).
loop(N) ->
receive
{read, Pid} ->
Pid ! {value, self(), N},
loop(N);
{write, Pid, M} ->
Pid ! {write_reply, self()},
loop(M);
{write2, _Pid, M} -> loop(M)
end.
read(Serv) ->
Serv ! {read, self()},
receive {value, Serv, N} -> N end.
write(Serv, N) ->
Serv ! {write, self(), N},
receive {write_reply, Serv} -> ok end.
write2(Serv, N) ->
Serv ! {write2, self(), N},
ok.
I understand that different values could be printed by the three different processes created in test/0
, but I'm trying to figure out the lowest and highest values that could be printed by those Parent
, Child1
and Child2
processes. The answer states:
Parent
: lowest 1, highest 5
Child1
: lowest 1, highest 5
Child2
: lowest 1, highest 2
Can somebody explain this?
Keep in mind that Erlang guarantees message order only from one process to another. If process A sequentially sends message 1 and then message 2 to process B, then B will receive them in that order. But Erlang guarantees no specific ordering of messages arriving at B if multiple concurrent processes are sending them. In this example, Parent
, Child1
, and Child2
all run concurrently and all send messages concurrently to Server
.
The Parent
process performs the following sequential steps:
- Spawns the
Server
process. This eventually sets the initial value in the Server
loop to 0
.
- Spawns
Child1
. This eventually writes the value 2
to Server
, then reads from Server
and prints the result.
- Uses
write/2
to send the value 3
to Server
. The write
case in the loop/1
function first replies to the caller and then installs the value 3
for its next iteration.
- Uses
write2/2
to send 5
to Server
. The write2/2
function just sends a message to Server
and does not await a reply. The write2
case in the loop/1
function just installs the value 5
for its next iteration.
- Spawns
Child2
, which eventually calls write2/2
with the value 1
, and then reads Server
and prints the result.
- Reads
Server
and prints the result.
For Parent
, step 3 sends the value 3
to Server
, so as far as Parent
is concerned, Server
now has the value 3
. In step 4, Parent
calls write2/2
to send 5
to the server, and that message must arrive at Server
sometime after the message sent in step 3. In step 6, Parent
performs a read, but all we know is that this message has to arrive at Server
after the write message in step 4. This message ordering means the highest value Parent
can see is 5
.
The lowest value Parent
can see is 1
because if Child2
gets its write message of 1
to Server
after the Parent
write message of 5
but before the final Parent
read message, then Parent
will see the 1
.
For Child1
, the highest value it can see is 5
because it runs concurrently with Parent
and the two messages sent by Parent
could arrive at Server
before its write message of 2
. The lowest Child1
can see is 1
because the Child2
write message of 1
can arrive before the Child1
read message.
For Child2
, the lowest value it can see is its own write of 1
. The highest value it can see is 2
from the write of Child1
because the Parent
writes of 3
and 5
occur before Child2
is spawned, thus Child1
is the only process writing concurrently and so only it has a chance of interleaving its write message between the Child2
write and read messages.