在并行环境中没有互斥锁的情况下 ref 是否可以安全使用

Raw*_*ler 5 ocaml mutex

我正在使用该库编写一个相当异步的程序Thread。我将有很多不同的线程需要访问string list ref,如果不使用 a ,这安全吗Mutex?我今天早些时候了解到这Hashtbl需要 a Mutex,但我找不到有关 的任何信息ref

oct*_*ron 6

简而言之,如果您对可变共享资源进行并发写入,则应该使用互斥体(或原子)来保护它们。

更详细地说,有三点需要牢记。

首先,OCamlthreads不是并行的。在 OCaml 4 及之前版本中,OCaml 运行时使用运行时锁来确保在任何时间点只有一个 OCaml 线程执行。在 OCaml 5 中,并行性的单位是域,为了保持向后兼容性,每个域都使用域锁来确保域中只有一个 OCaml 线程执行。

其次,OCaml 始终是内存安全的。即使存在竞争条件,内存访问也能保证类型正确。作为参考,这意味着您从参考中读取的所有值都将是写入参考中的值;而不是你的程序状态的一些奇怪的合并。

但是,如果没有同步,就不能保证并发程序的行为与等效的顺序程序相同。

例如,以下程序将到达assert false子句

let global = ref []
let sum = ref 0
let incr () =
  let state = !global in
  let a = Array.init 1_000 Fun.id in
  let updated = a :: state in
  global := updated

let decr () =
  match !global with
  | [] -> assert false
  | _ :: q ->
     global := q

let balanced () =
  let incrs = List.init 100 (fun _ -> Thread.create incr ()) in
  let () = List.iter Thread.join incrs in
  let decrs = List.init 100 (fun _ -> Thread.create decr ()) in
  List.iter Thread.join decrs

let () =
   while true do balanced () done
Run Code Online (Sandbox Code Playgroud)

即使所有对incr和 的调用decr都非常平衡。原因是read和中write的共享引用不能保证同时发生。因此,两个调用可能会以这种方式交错:globalincrdecrincr

(* first call to incr *)               | (* Second call to incr *)                   
  let state = !global in               |
  let a = Array.init 1_000 Fun.id in   |
                                       | let state = !global in               
  let updated = a :: state in          |
  global := updated                    | let a = Array.init 1_000 Fun.id in 
                                       | let updated = a :: state in  
                                       | global := updated
Run Code Online (Sandbox Code Playgroud)

这意味着第二次调用将incr删除第一个调用所做的更新,并且在两次调用后,incr我们最终在全局列表中只有一个新元素。

因此,只要您可能对同一可变共享资源进行并发写入,同步原语就必不可少。

第三,在 OCaml 5(也称为并行性)中,引用不能用作同步原语。这就是引用和原子之间的区别。特别是,如果您有

module M: sig
   val update: unit -> unit
   val read: unit -> int option
end = struct
let written = ref false
let answer = ref 0

let update () =
  answer := 1;
  written := true

let read () =
  if !written then Some !answer else None
end
Run Code Online (Sandbox Code Playgroud)

那么在某些CPU架构上,它可能会发生而不是read ()返回Some 0,因为不能保证answer在写入之前看到写入written