Atomic(3)
β¨ NAME β¨
Atomic β atomic references π
(this is page 3 of the OCaml library manual, bestie, and it's literally the whole module)
π¦ Module π¦
Module Atomic
: sig end
Atomic references. That's the vibe β¨
Peep Atomic.examples below for the actual glow-up, and if you're feeling brave there's a whole chapter called 'Memory model: The hard bits' in the manual β yes it's as hard as it sounds, iykyk π
Since 4.12 π
πΈ type !'a t πΈ
An atomic (mutable) reference to a value of type 'a. It's giving "a box that multiple threads can touch without chaos" π
πΈ val make : 'a -> 'a t πΈ
Create an atomic reference. Simple as. β¨
πΈ val make_contended : 'a -> 'a t πΈ
Create an atomic reference that is alone on a cache line. Heads up: it occupies 4-16x the memory of one allocated with make v π
The primary purpose is to prevent false-sharing and the resulting performance degradation. When a CPU performs an atomic operation, it temporarily takes ownership of an entire cache line that contains the atomic reference. If multiple atomic references share the same cache line, modifying these disjoint memory regions simultaneously becomes impossible, which can create a bottleneck. Hence, as a general guideline, if an atomic reference is experiencing contention, assigning it its own cache line may enhance performance.
Since 5.2 π«Ά
πΈ val get : 'a t -> 'a πΈ
Get the current value of the atomic reference. Just... look at it π
πΈ val set : 'a t -> 'a -> unit πΈ
Set a new value for the atomic reference. New number, who dis π
πΈ val exchange : 'a t -> 'a -> 'a πΈ
Set a new value for the atomic reference, and return the current value. You get the new one and the old one β iconic trade deal π
πΈ val compare_and_set : 'a t -> 'a -> 'a -> bool πΈ
compare_and_set r seen v sets the new value of r to v only 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 false otherwise. It's giving "check the vibe before you commit" π
πΈ val fetch_and_add : int t -> int -> int πΈ
fetch_and_add r n atomically increments the value of r by n, and returns the current value (before the increment). Yes, before β that part is important bestie π
πΈ val incr : int t -> unit πΈ
incr r atomically increments the value of r by 1. Tiny bump, big energy π₯
πΈ val decr : int t -> unit πΈ
decr r atomically decrements the value of r by 1. The reverse slay β¨
πΈ module Loc : sig end πΈ
Atomic "locations", such as record fields. Little addresses you can point at π―
Since 5.4 π¦
π§ Examples π§
π Basic Thread Coordination
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. π (no cap, it's random)
(* 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
π¦ Treiber Stack
Another example is a basic Treiber stack (a thread-safe stack) that can be safely shared between threads.
Note how both push and pop are recursive, because they attempt to swap the new stack (with one more, or one fewer, element) with the old stack. 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 l with 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 again. Manifesting success is not a strategy, bestie β retry πβ¨
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
Look at that β popped in LIFO order, exactly like the universe intended π
That's the whole page, babe π«Ά β written by OCamldoc, dated 2026-06-22, for Atomic(3) π go be atomic π
π¦ where this page comes from
- arch rolling: ocaml 5.5.0-1 Β· LGPL2.1 AND custom: QPL-1.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms