Mutexes in Racket

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.

#lang racket

(require racket/hash)

; Container holds a hash of counters; since we want to
; update it concurrently from multiple threads, we
; add a semaphore to synchronize access.
(struct container (semaphore counters))

; Lock the semaphore before accessing `counters`; unlock
; it at the end of the function using dynamic-wind.
(define (inc c name)
  (dynamic-wind
    (lambda () (semaphore-wait (container-semaphore c)))
    (lambda ()
      (hash-update! (container-counters c) name add1 0))
    (lambda () (semaphore-post (container-semaphore c)))))

(define (main)
  (define c (container (make-semaphore 1)
                       (hash 'a 0 'b 0)))

  ; This function increments a named counter
  ; in a loop.
  (define (do-increment name n)
    (for ([i (in-range n)])
      (inc c name)))

  ; Run several threads concurrently; note
  ; that they all access the same `container`,
  ; and two of them access the same counter.
  (define threads
    (list (thread (lambda () (do-increment 'a 10000)))
          (thread (lambda () (do-increment 'a 10000)))
          (thread (lambda () (do-increment 'b 10000)))))

  ; Wait for the threads to finish
  (for-each thread-wait threads)
  
  (println (container-counters c)))

(main)

Running the program shows that the counters updated as expected.

$ racket mutexes.rkt
'#hash((a . 20000) (b . 10000))

In this Racket version:

  1. We use a semaphore instead of a mutex, as Racket doesn’t have a direct equivalent to Go’s sync.Mutex.
  2. The container is implemented as a struct containing a semaphore and a hash table (Racket’s equivalent of Go’s map).
  3. The inc function uses dynamic-wind to ensure the semaphore is properly released, similar to Go’s defer.
  4. We use Racket’s thread function to create concurrent threads, which are similar in concept to goroutines.
  5. Instead of a WaitGroup, we simply collect the threads in a list and use for-each with thread-wait to wait for all threads to complete.

This example demonstrates how to use semaphores in Racket to safely access shared state from multiple threads, achieving the same goal as the original Go program with mutexes.

Next we’ll look at implementing this same state management task using only threads and channels.