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.