How to make threads communicate with each other in Erlang

In traditional programming languages, concurrency can be challenging and often requires the implementation of complex synchronization mechanisms to prevent conflicts between threads. However, Erlang provides a built-in concurrency model that simplifies designing and implementing concurrent systems.

Understanding threads in Erlang

In Erlang, threads are also known as schedulers, and they are responsible for executing Erlang processes. Each scheduler can run multiple processes concurrently, and the number of schedulers can be configured based on the available hardware resources.

While processes and threads are used for concurrency in Erlang, the two terms usually have a key difference. Processes have their own memory space, whereas threads share memory space with other threads within the same process. However, generally, threads of execution in Erlang share no data, which is why they are usually called processes, and we can use the terms interchangeably if we aren’t diving deep into the specifics.

Communication between threads

A process can send a message to another process using the ! operator, for example:

Pid ! Message

Here Pid is the process ID of the receiving process, and Message is the message being sent.

The receiving process can wait for incoming messages using the receive statement, which has the following format:

receive
   Pattern1 ->
       Actions1;
   Pattern2 ->
       Actions2;
   ...
   PatternN ->
       ActionsN
end.

Here, Pattern1 through PatternN are patterns that incoming messages are matched against.

The first pattern matches an incoming message, and the corresponding actions are executed. If no pattern matches, the process blocks until a matching message is received.

Example of communication between threads

Here is an example of a message passing between two processes that send messages to each other a number of times:

-module(main).
-export([start/0, thread1/2, thread2/0]).

thread1(0, Thread2_PID) ->
    Thread2_PID ! finished,
    io:format("Thread1 finished~n", []);

thread1(N, Thread2_PID) ->
    Thread2_PID ! {thread1, self()},
    receive
        thread2 ->
            io:format("Thread1 received message from Thread2~n", [])
    end,
    thread1(N - 1, Thread2_PID).

thread2() ->
    receive
        finished ->
            io:format("Thread2 finished~n", []);
        {thread1, Thread1_PID} ->
            io:format("Thread2 received message from Thread1~n", []),
            Thread1_PID ! thread2,
            thread2()
    end.

start() ->
    Thread2_PID = spawn(main, thread2, []),
    spawn(main, thread1, [3, Thread2_PID]).

In this example, thread1 and thread2 are two processes that send messages to each other. start/0 creates the two processes and allows them to communicate. The thread1/2 function sends messages to thread2, and the thread2/0 function waits for incoming messages and responds to them.

Here’s an example of how this module can be used:

1> c(main).
2> main:start().
Thread2 received message from Thread1
Thread1 received message from Thread2
Thread2 received message from Thread1
Thread1 received message from Thread2
Thread2 received message from Thread1
Thread1 received message from Thread2
Thread1 finished
Thread2 finished

This output shows the messages sent and received by the two processes. The output begins with Thread2 received a message from Thread1 because thread2 is the first process that receives a message.

Conclusion

The message-passing mechanism allows threads/processes to communicate and synchronize in Erlang.

This approach ensures that processes run independently and asynchronously, avoiding common issues in other concurrent programming models, such as deadlocks, race conditions, and shared-memory inconsistencies.

Free Resources

Copyright ©2025 Educative, Inc. All rights reserved