Atomic Counters in Scheme

Our example demonstrates the use of atomic operations for managing state in a concurrent environment. In Scheme, we don’t have built-in atomic operations, so we’ll simulate this behavior using a mutex (mutual exclusion) mechanism.

(import (rnrs) (rnrs mutable-pairs) (srfi :18))

;; We'll use a pair to represent our counter, with the car being the value
;; and the cdr being the mutex to protect it.
(define ops (cons 0 (make-mutex)))

;; A procedure to atomically increment the counter
(define (atomic-add! n)
  (mutex-lock! (cdr ops))
  (set-car! ops (+ (car ops) n))
  (mutex-unlock! (cdr ops)))

;; A procedure to atomically read the counter
(define (atomic-load)
  (mutex-lock! (cdr ops))
  (let ((value (car ops)))
    (mutex-unlock! (cdr ops))
    value))

;; Create a list of threads
(define threads
  (map (lambda (_)
         (make-thread
          (lambda ()
            ;; Each thread increments the counter 1000 times
            (do ((i 0 (+ i 1)))
                ((= i 1000))
              (atomic-add! 1)))))
       (iota 50)))

;; Start all threads
(for-each thread-start! threads)

;; Wait for all threads to finish
(for-each thread-join! threads)

;; Print the final value of the counter
(display "ops: ")
(display (atomic-load))
(newline)

In this Scheme version:

  1. We use a pair to represent our counter, where the car is the actual count and the cdr is a mutex to protect access to it.

  2. We define atomic-add! and atomic-load procedures to safely modify and read the counter, respectively. These use the mutex to ensure thread safety.

  3. Instead of goroutines, we create 50 threads using SRFI-18’s thread functions. Each thread increments the counter 1000 times.

  4. We start all threads and then wait for them to finish using thread-start! and thread-join!.

  5. Finally, we print the value of the counter.

To run this program, save it to a file (e.g., atomic-counter.scm) and run it with a Scheme implementation that supports SRFI-18, such as Chez Scheme:

$ chez --program atomic-counter.scm
ops: 50000

We expect to get exactly 50,000 operations. If we hadn’t used the mutex to protect access to the counter, we might get a different number, changing between runs, due to race conditions between threads.

This example demonstrates how to implement thread-safe operations in Scheme, which is crucial when dealing with shared state in concurrent programs.