Erlang - Concurrency


Advertisements


Concurrent programming in Erlang needs to have the following basic principles or processes.

The list includes the following principles −

piD = spawn(Fun)

Creates a new concurrent process that evaluates Fun. The new process runs in parallel with the caller. An example is as follows −

Example

Live Demo
-module(helloworld). 
-export([start/0]). 

start() ->
   spawn(fun() -> server("Hello") end). 

server(Message) ->
   io:fwrite("~p",[Message]).

The output of the above program is −

Output

“Hello”

Pid ! Message

Sends a message to the process with identifier Pid. Message sending is asynchronous. The sender does not wait but continues with what it was doing. ‘!’ is called the send operator.

An example is as follows −

Example

Live Demo
-module(helloworld). 
-export([start/0]). 
start() -> 
   Pid = spawn(fun() -> server("Hello") end), 
   Pid ! {hello}. 

server(Message) ->
   io:fwrite("~p",[Message]).

Receive…end

Receives a message that has been sent to a process. It has the following syntax −

Syntax

receive
Pattern1 [when Guard1] ->
Expressions1;
Pattern2 [when Guard2] ->
Expressions2;
...
End

When a message arrives at the process, the system tries to match it against Pattern1 (with possible guard Guard1); if this succeeds, it evaluates Expressions1. If the first pattern does not match, it tries Pattern2, and so on. If none of the patterns matches, the message is saved for later processing, and the process waits for the next message.

An example of the entire process with all 3 commands is shown in the following program.

Example

Live Demo
-module(helloworld). 
-export([loop/0,start/0]). 

loop() ->
   receive 
      {rectangle, Width, Ht} -> 
         io:fwrite("Area of rectangle is ~p~n" ,[Width * Ht]), 
         loop(); 
      {circle, R} ->
      io:fwrite("Area of circle is ~p~n" , [3.14159 * R * R]), 
      loop(); 
   Other ->
      io:fwrite("Unknown"), 
      loop() 
   end. 

start() ->
   Pid = spawn(fun() -> loop() end), 
   Pid ! {rectangle, 6, 10}.

The following things need to be noted about the above program −

  • The loop function has the receive end loop. So when a message is sent , it will processed by the receive end loop.

  • A new process is spawned which goes to the loop function.

  • The message is sent to the spawned process via the Pid ! message command.

The output of the above program is −

Output

Area of the Rectangle is 60

Maximum Number of Processes

In concurrency it is important to determine the maximum number of processes that are allowed on a system. You should then be able to understand how many process can execute concurrently on a system.

Let’s see an example of how we can determine what is the maximum number of processes that can execute on a system.

Live Demo
-module(helloworld). 
-export([max/1,start/0]). 

max(N) -> 
   Max = erlang:system_info(process_limit), 
   io:format("Maximum allowed processes:~p~n" ,[Max]), 
   
   statistics(runtime), 
   statistics(wall_clock), 
   
   L = for(1, N, fun() -> spawn(fun() -> wait() end) end), 
   {_, Time1} = statistics(runtime), 
   {_, Time2} = statistics(wall_clock), lists:foreach(fun(Pid) -> Pid ! die end, L), 
   
   U1 = Time1 * 1000 / N, 
   U2 = Time2 * 1000 / N, 
   io:format("Process spawn time=~p (~p) microseconds~n" , [U1, U2]).
   wait() -> 
   
   receive 
      die -> void 
   end. 
 
for(N, N, F) -> [F()]; 
for(I, N, F) -> [F()|for(I+1, N, F)]. 

start()->
   max(1000), 
   max(100000).

On any machine which has a good processing power, both of the above max functions will pass. Following is a sample output from the above program.

Maximum allowed processes:262144
Process spawn time=47.0 (16.0) microseconds
Maximum allowed processes:262144
Process spawn time=12.81 (10.15) microseconds

Receive with a Timeout

Sometimes a receive statement might wait forever for a message that never comes. This could be for a number of reasons. For example, there might be a logical error in our program, or the process that was going to send us a message might have crashed before it sent the message. To avoid this problem, we can add a timeout to the receive statement. This sets a maximum time that the process will wait to receive a message.

Following is the syntax of the receive message with a timeout specified

Syntax

receive 
Pattern1 [when Guard1] -> 
Expressions1; 

Pattern2 [when Guard2] ->
Expressions2; 
... 
after Time -> 
Expressions 
end

The simplest example is to create a sleeper function as shown in the following program.

Example

-module(helloworld). 
-export([sleep/1,start/0]). 

sleep(T) ->
   receive 
   after T -> 
      true 
   end. 
   
start()->
   sleep(1000).

The above code will sleep for 1000 Ms before actually exiting.

Selective Receive

Each process in Erlang has an associated mailbox. When you send a message to the process, the message is put into the mailbox. The only time this mailbox is examined is when your program evaluates a receive statement.

Following is the general syntax of the Selective receive statement.

Syntax

receive 
Pattern1 [when Guard1] ->
Expressions1; 

Pattern2 [when Guard1] ->
Expressions1; 
... 
after 
Time ->
ExpressionTimeout 
end

This is how the above receive statement works −

  • When we enter a receive statement, we start a timer (but only if an after section is present in the expression).

  • Take the first message in the mailbox and try to match it against Pattern1, Pattern2, and so on. If the match succeeds, the message is removed from the mailbox, and the expressions following the pattern are evaluated.

  • If none of the patterns in the receive statement matches the first message in the mailbox, then the first message is removed from the mailbox and put into a “save queue.” The second message in the mailbox is then tried. This procedure is repeated until a matching message is found or until all the messages in the mailbox have been examined.

  • If none of the messages in the mailbox matches, then the process is suspended and will be rescheduled for execution the next time a new message is put in the mailbox. Note that when a new message arrives, the messages in the save queue are not rematched; only the new message is matched.

  • As soon as a message has been matched, then all messages that have been put into the save queue are reentered into the mailbox in the order in which they arrived at the process. If a timer was set, it is cleared.

  • If the timer elapses when we are waiting for a message, then evaluate the expressions ExpressionsTimeout and put any saved messages back into the mailbox in the order in which they arrived at the process.



Advertisements