Atomic Counters in Standard ML

The primary mechanism for managing state in Standard ML is through immutable data structures and functional programming patterns. However, we can simulate atomic counters using mutable references and threads.

structure AtomicCounter = struct
  (* We'll use a mutable reference to represent our counter *)
  val ops = ref 0

  (* A function to increment the counter *)
  fun increment () = 
    (ops := !ops + 1)

  (* A function to get the current value *)
  fun get () = !ops
end

(* We'll use the Thread structure for concurrent execution *)
structure Thread = Thread

(* A function to simulate a worker *)
fun worker () =
  let
    fun loop 0 = ()
      | loop n = (AtomicCounter.increment(); loop (n-1))
  in
    loop 1000
  end

(* Main function *)
fun main () =
  let
    (* We'll start 50 threads that each increment the counter 1000 times *)
    val threads = List.tabulate(50, fn _ => Thread.create worker)
  in
    (* Wait for all threads to finish *)
    List.app Thread.join threads;
    
    (* Print the final counter value *)
    print ("ops: " ^ Int.toString (AtomicCounter.get()) ^ "\n")
  end

(* Run the main function *)
val _ = main()

In this Standard ML version:

  1. We define an AtomicCounter structure that encapsulates a mutable reference ops and provides increment and get functions.

  2. We use the Thread structure for concurrent execution, which is similar to goroutines in Go.

  3. The worker function simulates the work done by each thread, incrementing the counter 1000 times.

  4. In the main function, we create 50 threads using List.tabulate, each running the worker function.

  5. We wait for all threads to finish using List.app Thread.join threads.

  6. Finally, we print the total number of operations.

To run this program, you would typically save it in a file (e.g., atomic_counter.sml) and use an SML compiler or interpreter. For example, with Standard ML of New Jersey (SML/NJ):

$ sml atomic_counter.sml
ops: 50000

Note that the exact behavior may depend on the specific Standard ML implementation and its thread support. Some implementations might not provide true parallelism, which could affect the results.

Also, unlike Go’s sync/atomic package, this implementation doesn’t guarantee atomic operations. In a real-world scenario, you might need to use additional synchronization mechanisms to ensure thread safety.

Next, we’ll look at other concurrency primitives in Standard ML.