Erlang has the ability to "listen" for messages from different senders
In which order will they be process?
Can we force an order?
The semantics for a receive
statement
A receive statement tries to find a match as early in the mailbox as it can
receive msg3 -> 42 end
Another example
receive msg4 -> 42 end
Waiting for multiple messages
receive msg4 -> 42 _ -> 41 end
The _
matches with any message in the mailbox.
Multiple messages can come from different processes (code)
-module(echo2). -export([start/0]). echo() -> receive {From, Msg} -> timer:sleep(random:uniform(100)), From ! {self(), Msg}, echo(); stop -> true end. start() -> PidB = spawn(fun echo/0), PidC = spawn(fun echo/0), % sending tokens Token = 42, PidB ! {self(), Token}, io:format("Sent~w~n",[Token]), Token2 = 41, PidC ! {self(), Token2}, io:format("Sent~w~n",[Token2]), % receive messages receive {PidB, Msg} -> io:format("Received from B: ~w~n", [Msg]) ; {PidC, Msg} -> io:format("Received from C: ~w~n", [Msg]) end, % stop echo-servers PidB ! stop, PidC ! stop.
Function timer:sleep(N)
sleeps a process for N
milliseconds
Function random:uniform(N)
produces a random integer
between 1
and N
Distinguish the source by Pid
s
Multiple messages can come from the same processes (code)
Erlang has asynchronous messages
Send several messages of the same shape and continue
computing
When receiving the responses, how can the code match them to the appropriate request?
-module(echo3). -export([start/0]). echo() -> receive {From, Msg} -> From ! {self(), Msg}, echo(); stop -> true end. start() -> PidB = spawn(fun echo/0), % sending tokens Token = 42, PidB ! {self(), Token}, io:format("Sent~w~n",[Token]), Token2 = 41, PidB ! {self(), Token2}, io:format("Sent~w~n",[Token2]), % receive messages receive {PidB, Msg} -> io:format("Received 41? ~w~n", [Msg]) ; {PidB, Msg} -> io:format("Received 42? ~w~n", [Msg]) end, % stop echo-servers PidB ! stop.
BIF make_ref
provides globally unique reference
objects (references for short) different from every
other object in the Erlang system including remote nodes
References can be used to uniquely identify messages! (code)
-module(echo4). -export([start/0]). echo() -> receive {From, Ref, Msg} -> From ! {self(), Ref, Msg}, echo(); stop -> true end. start() -> PidB = spawn(fun echo/0), % sending tokens Token = 42, Ref = make_ref(), PidB ! {self(), Ref, Token}, io:format("Sent~w~n",[Token]), Token2 = 41, Ref2 = make_ref(), PidB ! {self(), Token2}, io:format("Sent~w~n",[Token2]), % receive messages receive {PidB, Ref2, Msg} -> io:format("Received 41? ~w~n", [Msg]) ; {PidB, Ref, Msg} -> io:format("Received 42? ~w~n", [Msg]) end, % stop echo-servers PidB ! stop.
Clauses can have guards
Guards must be composed from terminating functions (BIFs)
receive {Pid, Ref, N} when N>0 -> ...
Common asynchronous communication pattern
Off-loading heavy mathematical operations
Server code
loop(Count) -> receive stop -> true ; {get_count, From, Ref} -> From ! {result, Ref, Count}, loop(Count); {factorial, From, Ref, N} -> Result = factorial(N), From ! {result, Ref, Result}, loop(Count+1) end. % starting server with initial state 0 start() -> spawn(fun() -> loop(0) end).
Client code
compute_factorial(Pid, N) -> Ref = make_ref(), Pid ! {factorial, self(), Ref, N}, receive {result, Ref, Result} -> Result end.
A simple test
> c(math_examples). {ok,math_examples} > math_examples:factorial(10). 3628800 > mserver:start(). <0.40.0> > mserver:compute_factorial(list_to_pid("<0.40.0>"),10). 3628800
What if the server crashes or stop?
> list_to_pid("<0.40.0>") ! stop > mserver:compute_factorial(list_to_pid("<0.40.0>"),10). * 2: syntax error before: mserver > mserver:start(). <0.44.0> > mserver:compute_factorial(list_to_pid("<0.44.0>"),10). 3628800
Processes IDs are dynamic but code is static!
Erlang has a method for publishing a process identifier
BIF register
% starting server with initial state 0 start() -> Pid = spawn(fun() -> loop(0) end), register(server,Pid).
The atom server
can be used instead of a concrete process ID
> mserver2:start(). true > mserver2:compute_factorial(server,10). 3628800 > server ! stop. stop > mserver2:compute_factorial(server,10). ** exception error: bad argument in function mserver2:compute_factorial/2 > mserver2:start(). true > mserver2:compute_factorial(server,10). 3628800
Message passing abstractions extend easily for distributed environments
Erlang nodes
An instance of an Erlang runtime system
Nodes can easily communicate with each other
Creating a node
erl -name 'nodeS@127.0.0.1' -setcookie lecture
Creating two nodes (for simplicity on the same machine)
erl -name 'nodeS@127.0.0.1' -setcookie lecture erl -name 'nodeC@127.0.0.1' -setcookie lecture
Connecting nodes
nodeC@127.0.0.1
(nodeC@127.0.0.1)> net_adm:ping('nodeS@127.0.0.1'). pong (nodeC@127.0.0.1)> nodes(). ['nodeS@127.0.0.1']
Running your code
(nodeC@127.0.0.1)> nl(math_examples). abcast (nodeC@127.0.0.1)> nl(mserver2). abcast
nodeS
node (nodeS@127.0.0.1)> mserver2:start(). true
(nodeC@127.0.0.1)> mserver2:compute_factorial({server, 'nodeS@127.0.0.1'}, 10). 3628800
Use of {registered_name, node@IP}
instead of the process ID
(e.g. <0.47.0>
) or only the registered name (e.g. server
)
The code has not been changed for running in a distributed setting!