Erlang server request order

2019-09-18 03:49发布

问题:

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?

回答1:

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:

  1. Spawns the Server process. This eventually sets the initial value in the Server loop to 0.
  2. Spawns Child1. This eventually writes the value 2 to Server, then reads from Server and prints the result.
  3. 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.
  4. 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.
  5. Spawns Child2, which eventually calls write2/2 with the value 1, and then reads Server and prints the result.
  6. 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.