Mutexes in Clojure

Our example demonstrates how to manage complex state using a mutex to safely access data across multiple threads.

(ns mutexes-example.core
  (:require [clojure.core.async :as async]))

;; Container holds a map of counters; since we want to
;; update it concurrently from multiple threads, we
;; add an atom to synchronize access.
(defrecord Container [counters])

(defn inc-counter [container name]
  (swap! (:counters container) update name (fnil inc 0)))

(defn -main []
  (let [container (->Container (atom {"a" 0, "b" 0}))
        increment-fn (fn [name n]
                       (dotimes [_ n]
                         (inc-counter container name)))
        threads (for [[name n] [["a" 10000] ["a" 10000] ["b" 10000]]]
                  (Thread. #(increment-fn name n)))]
    
    ;; Start all threads
    (run! #(.start %) threads)
    
    ;; Wait for all threads to finish
    (run! #(.join %) threads)
    
    (println @(:counters container))))

(-main)

In this Clojure version:

  1. We define a Container record that holds an atom containing a map of counters.

  2. The inc-counter function uses swap! to atomically update the counter for a given name.

  3. In the -main function, we create a container and define an increment-fn that increments a named counter in a loop.

  4. We create three threads, two incrementing “a” and one incrementing “b”, each 10000 times.

  5. We start all threads, wait for them to finish, and then print the final state of the counters.

To run the program:

$ clj -M mutexes.clj
{"a" 20000, "b" 10000}

Running the program shows that the counters are updated as expected, demonstrating thread-safe concurrent access to shared state.

This example illustrates how Clojure’s atoms provide a simple and effective way to manage shared, mutable state in a concurrent environment, similar to using mutexes in other languages.