Mutexes in OCaml
In the previous example, we saw how to manage simple counter state using atomic operations. For more complex state, we can use a mutex to safely access data across multiple threads.
open Lwt
open Lwt.Infix
module StringMap = Map.Make(String)
type container = {
mutex: Lwt_mutex.t;
mutable counters: int StringMap.t;
}
let inc c name =
Lwt_mutex.with_lock c.mutex (fun () ->
let count = StringMap.find_opt name c.counters |> Option.value ~default:0 in
c.counters <- StringMap.add name (count + 1) c.counters;
Lwt.return_unit
)
let main () =
let c = {
mutex = Lwt_mutex.create ();
counters = StringMap.empty |> StringMap.add "a" 0 |> StringMap.add "b" 0;
} in
let do_increment name n =
let rec loop i =
if i = n then
Lwt.return_unit
else
inc c name >>= fun () ->
loop (i + 1)
in
loop 0
in
let tasks = [
do_increment "a" 10000;
do_increment "a" 10000;
do_increment "b" 10000;
] in
Lwt.join tasks >>= fun () ->
Lwt_io.printf "Counters: %s\n"
(c.counters |> StringMap.bindings |>
List.map (fun (k, v) -> Printf.sprintf "%s: %d" k v) |>
String.concat ", ")
let () = Lwt_main.run (main ())
In this OCaml version, we use the Lwt
library for concurrency and Lwt_mutex
for mutual exclusion. The Container
struct is replaced with a record type container
, which holds a mutex and a mutable map of counters.
The inc
function now takes a container
and a name, and uses Lwt_mutex.with_lock
to ensure thread-safe access to the counters.
In the main
function, we create a container with initial counters. The do_increment
function is defined as a local function that increments a named counter in a loop.
We then create three tasks using do_increment
and use Lwt.join
to wait for all tasks to complete. Finally, we print the results.
To run the program:
$ ocamlbuild -pkg lwt.unix mutex_example.native
$ ./mutex_example.native
Counters: a: 20000, b: 10000
This example demonstrates how to use mutexes in OCaml to safely access shared state from multiple concurrent tasks. The Lwt_mutex
ensures that only one thread can access the counters at a time, preventing race conditions.
Next, we’ll look at implementing this same state management task using only lightweight threads and channels in OCaml.