Data Centers

Synchronous message passing in Erlang

Erlang uses asynchronous message passing to enable communication between processes. For all of its advantages, asynchronous message passing can give you no assurances that the messages it sends are received. If your program needs those kind of guarantees, then you should go for a synchronous messaging approach.

Erlang uses asynchronous message passing to enable communication between processes. Asynchronous message passing is useful because it allows each component to be separate, simplifies the code involved in transmitting information between processes, and is the approach that involves the least amount of message passing delay.

Sometimes however you need something more — for all of its advantages, asynchronous message passing can give you no assurances that the messages it sends are received. If your program needs those kind of guarantees, then you should go for a synchronous messaging approach.

We'll start with a standard version of a common program pattern: producers and consumers. In this example we'll have a number of different threads which produce information and send it to a single consumer thread, which processes it.

-module(prodcon).
-export([start/0, consumer/0, producer/3]).

producer(_, _, 0) -> true;
producer(Me, Server, N) ->
	Server ! {Me, N},
	producer(Me, Server, N-1).

consumer() ->
	receive
		{Them, N} -> 
			io:format("~s ~w~n", [Them, N]),
			consumer()
	end.

start() ->
	Server = spawn(prodcon, consumer, []),
	spawn(prodcon, producer, ['A', Server, 10]),
	spawn(prodcon, producer, ['B', Server, 5]),
	io:format("finished start~n", []).

And the output when this is run in the Erlang interactive prompt:

1> c(prodcon).     
{ok,prodcon}
2> prodcon:start().
finished start
A 10
okA 9

A 8 
A 7 
A 6 
A 5 
A 4 
A 3 
A 2 
A 1
B 5
B 4
B 3
B 2
B 1

If we need proof of delivery, then we can have the consumer reply to each message it receives from a producer, and have each producer wait until it receives a reply from the consumer.

To do this, we need to make a small change to the code in addition to the replying and waiting code — we must send the Pid (Process Identifier) of the producer in every message it sends, so that the consumer knows which process to reply to.

The code of an asynchronous producer/consumer program is as follows:

-module(prodcon).
-export([start/0, consumer/0, producer/3]).

producer(_, _, 0) -> true;
producer(Me, Server, N) ->
	Server ! {self(), Me, N},
	receive {Server, ok} -> 
		true 
	end,
	producer(Me, Server, N-1).

consumer() ->
	receive
		{Pid, Them, N} -> 
			io:format("~s ~w~n", [Them, N]),
			Pid ! {self(), ok},
			consumer()
	end.

start() ->
	Server = spawn(prodcon, consumer, []),
	spawn(prodcon, producer, ['A', Server, 10]),
	spawn(prodcon, producer, ['B', Server, 5]),
	io:format("finished start~n", []).

Now when we run this, we get a different result:

41> c(prodcon).     
{ok,prodcon}
42> prodcon:start().
finished start
A 10
okB 5

A 9
B 4
A 8 
B 3 
A 7 
B 2 
A 6 
B 1 
A 5 
A 4 
A 3 
A 2 
A 1 

Be warned, these outputs may differ from your results — they depend on the scheduling of the processes inside the Erlang interpreter. It is possible that both programs generate output in the same order. The prompt output is included to demonstrate that using synchronous messages will likely have an effect on the order in which your code operates.

Asynchronous message passing is the best solution in the majority of cases when programming in Erlang. If you do find the need for replies however, you can see how they can be included in your code with only minor additions.

Editor's Picks

Free Newsletters, In your Inbox