Spec-Zone.ru › OCaml

Модуль Атомарный

module Atomic: sig .. end

Атомарные ссылки.

См. примеры ниже. См. главу «Модель памяти: Сложная часть» в руководстве.

  • Since 4.12
type !'a t 

Атомарная (изменяемая) ссылка на значение типа 'a.

val make : 'a -> 'a t

Создать атомарную ссылку.

val make_contended : 'a -> 'a t

Создать атомарную ссылку, которая расположена на отдельной строке кэша. Она занимает в 4-16 раз больше памяти, чем ссылка, выделенная с помощью make v.

Основное назначение — предотвратить ложное совместное использование и связанное с этим снижение производительности. Когда процессор выполняет атомную операцию, он временно получает право доступа ко всей строке кэша, содержащей атомную ссылку. Если несколько атомных ссылок используют одну и ту же строку кэша, одновременное изменение этих непересекающихся областей памяти становится невозможным, что может создать узкое место. Таким образом, как общее руководство, если атомарная ссылка испытывает конфликт, присвоение ей собственной строки кэша может улучшить производительность.

val get : 'a t -> 'a

Получить текущее значение атомарной ссылки.

val set : 'a t -> 'a -> unit

Установить новое значение для атомарной ссылки.

val exchange : 'a t -> 'a -> 'a

Установить новое значение для атомарной ссылки и вернуть текущее значение.

val compare_and_set : 'a t -> 'a -> 'a -> bool

compare_and_set r seen v устанавливает новое значение r на v только в том случае, если его текущее значение физически равно seen — сравнение и установка происходят атомарно. Возвращает true , если сравнение прошло успешно (следовательно, установка произошла), и false в противном случае.

val fetch_and_add : int t -> int -> int

fetch_and_add r n атомарно увеличивает значение r на n, и возвращает текущее значение (до увеличения).

val incr : int t -> unit

incr r атомарно увеличивает значение r на 1.

val decr : int t -> unit

decr r атомарно уменьшает значение r на 1.

Примеры

Основные координации потоков

Основной случай использования — это глобальные счетчики, которые обновляются безопасным для потоков способом, например, для сохранения некоторого рода метрик по операциям ввода-вывода, выполненным программой. Еще один основной случай использования — координация завершения потоков в данной программе, например, когда один поток находит ответ или когда программа завершается пользователем.

Здесь, например, мы попытаемся найти число, чей хэш удовлетворяет основному свойству. Для этого мы запустим несколько потоков, которые будут перебирать случайные числа, пока не найдут подходящее.

Конечно, вывод ниже является примером, и он будет изменяться каждый раз при запуске программы.

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

Стек Трейбера

Еще один пример — базовый стек Трейбера (безопасный для потоков стек), который можно безопасно использовать несколькими потоками.

Обратите внимание, что как push , так и pop рекурсивны, так как они пытаются поменять новый стек (с одним элементом больше или меньше) на старый стек. Это оптимистическая конкуренция: каждая итерация, например, push stack x получает старый стек l и надеется, что к тому моменту, когда он попытается заменить l на x::l, никому другому не удастся изменить список. Если compare_and_set не удастся, это означает, что мы были слишком оптимистичны, и должны попробовать снова.

    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
    

© 1995-2024 INRIA.
https://ocaml.org/manual/5.2/api/Atomic.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API