oracular (3) Atomic.3o.gz

Provided by: ocaml-man_5.2.0-3_all bug

NAME

       Atomic - Atomic references.

Module

       Module   Atomic

Documentation

       Module Atomic
        : sig end

       Atomic references.

       See Atomic.examples below.  See 'Memory model: The hard bits' chapter in the manual.

       Since 4.12

       type !'a t

       An atomic (mutable) reference to a value of type 'a .

       val make : 'a -> 'a t

       Create an atomic reference.

       val make_contended : 'a -> 'a t

       Create  an atomic reference that is alone on a cache line. 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.

       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 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.

       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).

       val incr : int t -> unit

       incr r atomically increments the value of r by 1 .

       val decr : int t -> unit

       decr r atomically decrements the value of r by 1 .

   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.

           (* 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.

           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