Mutexes in Erlang

In the previous example, we saw how to manage simple counter state using atomic operations. For more complex state, we can use a process to safely access data across multiple concurrent operations.

-module(mutexes).
-export([start/0]).

% Container holds a map of counters; since we want to
% update it concurrently from multiple processes, we
% use a separate process to manage the state.
start() ->
    Pid = spawn(fun() -> loop(#{a => 0, b => 0}) end),
    do_increment(Pid, "a", 10000),
    do_increment(Pid, "a", 10000),
    do_increment(Pid, "b", 10000),
    timer:sleep(100),  % Wait for all operations to complete
    Pid ! {get_counters, self()},
    receive
        {counters, Counters} ->
            io:format("~p~n", [Counters])
    end.

% The loop function manages the state of the counters
loop(Counters) ->
    receive
        {increment, Key} ->
            NewCounters = maps:update_with(Key, fun(V) -> V + 1 end, Counters),
            loop(NewCounters);
        {get_counters, From} ->
            From ! {counters, Counters},
            loop(Counters)
    end.

% This function increments a named counter in a loop
do_increment(Pid, Name, N) ->
    spawn(fun() ->
        lists:foreach(fun(_) ->
            Pid ! {increment, Name}
        end, lists:seq(1, N))
    end).

In this Erlang version, we use a separate process to manage the state of the counters. This approach is idiomatic in Erlang and provides thread-safe access to shared state without explicit locks.

The start/0 function initializes the counter process and spawns three processes to increment the counters concurrently. It then waits briefly for all operations to complete before retrieving and printing the final state of the counters.

The loop/1 function manages the state of the counters. It receives messages to either increment a counter or retrieve the current state of all counters.

The do_increment/3 function spawns a new process that sends increment messages to the counter process in a loop.

Running the program shows that the counters are updated as expected:

$ erl -noshell -s mutexes start -s init stop
#{a => 20000,b => 10000}

This example demonstrates how Erlang’s built-in concurrency model and message-passing paradigm can be used to manage shared state safely without explicit locks or mutexes.

Next, we’ll look at implementing this same state management task using only processes and message passing, which is more idiomatic in Erlang.