Processes
Message passing
Reacting to multiple messages
No share data!
Process is essentially another function running concurrently.
start() -> Pid = spawn(fun run/0), io:format("Spawned ~p~n",[Pid]). run() -> io:format("Hello!~n",[]).
The ,
is sequential composition (like ;
in Java)
Expression fun run/0
indicates to execute the function
run
which has zero arguments.
Function spawn
returns the proceed Id (Pid
) of the newly
created process for future interaction with it.
Spawning new process is also known as forking
This terminology comes from the operating system community
Look at the picture upside-down and you got a fork!
Built-in functions
spawn
, spawn_link
, atom_to_list
Don’t need importing
Mostly located in module called erlang
(more here)
So far we considered synchronisation mechanisms based on shared variables
What about networked (distributed) architectures?
No shared memory
Message passing is the natural model for such scenarios
Words by Joe Armstrong (Programming Erlang - Software for a concurrent world)
The programming world went one way (toward shared state). The Erlang community went the other way. (Few other languages followed the “message passing concurrency” road. Others were Oz and Occam.)
We don’t have shared memory. I have my memory. You have yours. We have two brains, one each. They are not joined together. To change your memory, I send you a message: I talk, or I wave my arms. You listen, you see, and your memory changes;
It is relatively easy to adapt Erlang programs for distributed environments
One process sends a message
Another process awaits for a message
Dimensions for message passing approaches:
What form of synchronisation is required
What form of process naming is involved in message passing
Consider the behaviour of the sender of a message
Asynchronous send
Synchronous send
Rendezvous / Remote invocation
Erlang has asynchronous message passing
Ada has rendezvous
Java has libraries
Sockets – asynchronous message passing
Remote Method Invocation – it can be seen as synchronous/rendezvous message passing
How do sender and receiver refer to each other when message passing is used?
Direct symmetric
Direct asymmetric
Indirect
Naming a process is not always convenient
Naming an intermediary can be more flexible
A channel, service name, or a mailbox
Potentially many-to-many communication
Asynchronous send
Receive from a mailbox
Spawn a function evaluation in a separate thread
Function should be written using tail recursion
Interaction
Process A sends a token to a Process B
Process B just returns it back to Process A
-module(echo). -export([start/0]). echo() -> receive {From, Msg} -> From ! {self(), Msg}, echo(); stop -> true end. start() -> Pid = spawn(fun echo/0), % sending tokens to the server Token = 42, Pid ! {self(), Token}, io:format("Sent~w~n",[Token]), receive {Pid, Msg} -> io:format("Received ~w~n", [Msg]) end, % stop server Pid ! stop.
{Pid, Msg}
, where Pid
is bound)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 -> ...