See the examples below. See 'Memory model: The hard bits' chapter in the manual.
An atomic (mutable) reference to a value of type
val make :
'a -> 'a t
Create an atomic reference.
val get :
'a t -> 'a
Get the current value of the atomic reference.
val set :
'a t -> 'a -> unit
Set a new value for the atomic reference.
val exchange :
'a t -> 'a -> 'a
Set a new value for the atomic reference, and return the current value.
val compare_and_set :
'a t -> 'a -> 'a -> bool
compare_and_set r seen v sets the new value of
if its current value is physically equal to
seen -- the
comparison and the set occur atomically. Returns
true if the
comparison succeeded (so the set happened) and
val fetch_and_add :
int t -> int -> int
fetch_and_add r n atomically increments the value of
and returns the current value (before the increment).
val incr :
int t -> unit
incr r atomically increments the value of
val decr :
int t -> unit
decr r atomically decrements the value of
A basic use case is to have global counters that are updated in a thread-safe way, for example to keep some sorts of metrics over IOs performed by the program. Another basic use case is to coordinate the termination of threads in a given program, for example when one thread finds an answer, or when the program is shut down by the user.
Here, for example, we're going to try to find a number whose hash satisfies a basic property. To do that, we'll run multiple threads which will try random numbers until they find one that works.
Of course the output below is a sample run and will change every time the program is run.
(* use for termination *) let stop_all_threads = Atomic.make false (* total number of individual attempts to find a number *) let num_attempts = Atomic.make 0 (* find a number that satisfies [p], by... trying random numbers until one fits. *) let find_number_where (p:int -> bool) = let rand = Random.State.make_self_init() in while not (Atomic.get stop_all_threads) do let n = Random.State.full_int rand max_int in ignore (Atomic.fetch_and_add num_attempts 1 : int); if p (Hashtbl.hash n) then ( Printf.printf "found %d (hash=%d)\n%!" n (Hashtbl.hash n); Atomic.set stop_all_threads true; (* signal all threads to stop *) ) done;; (* run multiple domains to search for a [n] where [hash n <= 100] *) let () = let criterion n = n <= 100 in let threads = Array.init 8 (fun _ -> Domain.spawn (fun () -> find_number_where criterion)) in Array.iter Domain.join threads; Printf.printf "total number of attempts: %d\n%!" (Atomic.get num_attempts) ;; - : unit = () found 1651745641680046833 (hash=33) total number of attempts: 30230350
Another example is a basic Treiber stack (a thread-safe stack) that can be safely shared between threads.
Note how both
pop are recursive, because they attempt to
swap the new stack (with one more, or one fewer, element) with the old
This is optimistic concurrency: each iteration of, say,
push stack x
gets the old stack
l, and hopes that by the time it tries to replace
x::l, nobody else has had time to modify the list. If the
compare_and_set fails it means we were too optimistic, and must try
type 'a stack = 'a list Atomic.t let rec push (stack: _ stack) elt : unit = let cur = Atomic.get stack in let success = Atomic.compare_and_set stack cur (elt :: cur) in if not success then push stack elt let rec pop (stack: _ stack) : _ option = let cur = Atomic.get stack in match cur with |  -> None | x :: tail -> let success = Atomic.compare_and_set stack cur tail in if success then Some x else pop stack # let st = Atomic.make  # push st 1 - : unit = () # push st 2 - : unit = () # pop st - : int option = Some 2 # pop st - : int option = Some 1 # pop st - : int option = None