Concurrency
Processes
- Forking
Message passing
- Synchronization and naming conventions
- Erlang approach
Reacting to multiple messages
- From the same origin as well as different sources
Processes
No share data!
- Different from threads
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 functionrun
which has zero arguments.Function
spawn
returns the proceed Id (Pid
) of the newly created process for future interaction with it.- To be explained later
Forking
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!
BIF
Built-in functions
spawn
,spawn_link
,atom_to_list
Don’t need importing
Mostly located in module called
erlang
(more here)
Message passing
So far we considered synchronisation mechanisms based on shared variables
- Concurrent programs require hardware in which processors share memory
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
How does it work?
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
Forms of synchronization
Consider the behaviour of the sender of a message
Asynchronous send
- Send and continue working (e-mail, SMS)
Synchronous send
- Send and wait for the message to be received (fax)
Rendezvous / Remote invocation
- Send and wait for reply (phone call)
Examples
Erlang has asynchronous message passing
Ada has rendezvous
- Previously used at Chalmers as a main teaching language
Java has libraries
Sockets – asynchronous message passing
Remote Method Invocation – it can be seen as synchronous/rendezvous message passing
Forms of naming
How do sender and receiver refer to each other when message passing is used?
Direct symmetric
Direct asymmetric
- Erlang has this naming convention
- Sockets
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
Erlang message passing
Asynchronous send
Receive from a mailbox
Spawning and tail recursion
Spawn a function evaluation in a separate thread
Function should be written using tail recursion
- Performance gain!
A simple echo-server
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.
- Observe that Process A waits only for a message from
Process B (
{Pid, Msg}
, wherePid
is bound)
Reacting to multiple messages
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
statementA 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.
- The oldest message is tried against every pattern of the receive until one of them matches.
Sources of multiple messages
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 forN
millisecondsFunction
random:uniform(N)
produces a random integer between1
andN
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 nodesReferences 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.
Selective receive
Clauses can have guards
Guards must be composed from terminating functions (BIFs)
receive {Pid, Ref, N} when N>0 -> ...