Mutexes in Lisp

In this example, we’ll demonstrate how to use a mutex to safely access shared data across multiple threads in Lisp. We’ll use the Common Lisp implementation for this example.

(defpackage :mutexes
  (:use :cl :bordeaux-threads))

(in-package :mutexes)

;; Container holds a hash table of counters; since we want to
;; update it concurrently from multiple threads, we
;; add a mutex to synchronize access.
(defclass container ()
  ((mutex :initform (make-lock))
   (counters :initform (make-hash-table :test #'equal))))

;; Lock the mutex before accessing counters; unlock
;; it at the end of the function using unwind-protect
;; (which is similar to defer in other languages).
(defmethod inc ((c container) name)
  (with-slots (mutex counters) c
    (with-lock-held (mutex)
      (setf (gethash name counters)
            (1+ (gethash name counters 0))))))

(defun main ()
  (let ((c (make-instance 'container)))
    ;; Initialize the counters
    (setf (gethash "a" (slot-value c 'counters)) 0)
    (setf (gethash "b" (slot-value c 'counters)) 0)

    ;; This function increments a named counter
    ;; in a loop.
    (flet ((do-increment (name n)
             (dotimes (i n)
               (inc c name))))

      ;; Run several threads concurrently; note
      ;; that they all access the same Container,
      ;; and two of them access the same counter.
      (let ((threads (list
                      (make-thread (lambda () (do-increment "a" 10000)))
                      (make-thread (lambda () (do-increment "a" 10000)))
                      (make-thread (lambda () (do-increment "b" 10000))))))

        ;; Wait for the threads to finish
        (mapc #'join-thread threads)

        ;; Print the final state of the counters
        (format t "~a~%" (slot-value c 'counters)))))

;; Run the main function
(main)

This Lisp code demonstrates the use of mutexes to manage concurrent access to shared state. Here’s a breakdown of the key components:

  1. We define a container class that holds a hash table of counters and a mutex.

  2. The inc method increments a named counter, using the mutex to ensure thread-safe access.

  3. In the main function, we create an instance of the container and initialize its counters.

  4. We define a local function do-increment that increments a named counter in a loop.

  5. We create three threads that concurrently increment the counters.

  6. We wait for all threads to finish using mapc and join-thread.

  7. Finally, we print the state of the counters.

To run this program, you’ll need to have a Common Lisp implementation installed, along with the Bordeaux Threads library for multi-threading support. Save the code to a file (e.g., mutexes.lisp) and load it into your Lisp REPL:

(load "mutexes.lisp")

The output should show that the counters were updated as expected:

#<HASH-TABLE :TEST EQUAL :COUNT 2 {10023FB5B3}>

You can inspect the hash table to see the final counter values:

(maphash #'(lambda (k v) (format t "~a: ~a~%" k v)) 
         (slot-value c 'counters))

This should output:

a: 20000
b: 10000

This example demonstrates how to use mutexes in Lisp to safely manage shared state in a multi-threaded environment, similar to the original Go example.