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:
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.
We define
atomic-add!
andatomic-load
procedures to safely modify and read the counter, respectively. These use the mutex to ensure thread safety.Instead of goroutines, we create 50 threads using SRFI-18’s thread functions. Each thread increments the counter 1000 times.
We start all threads and then wait for them to finish using
thread-start!
andthread-join!
.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.