在OCaml中共享POSIX线程之间的引用时线程安全

时间:2011-02-14 02:03:56

标签: thread-safety ocaml

让两个线程在OCaml中共享类型为int ref的状态的原始方法是:

let add i x =
    x := !x + i;
    Printf.printf "add %d: x is now %d\n" i !x;
    flush stdout

let rec run f x d =
    f x;
    Thread.delay d;
    run f x d

let _ =
    let x = ref 0 in
    ignore(Thread.create (run (add 1) x) 0.2);
    run (add 10) x 0.1

函数add只是将i添加到引用x中的整数。函数run只是将f应用于相同的参数x,每个应用程序之间的延迟为d。 main函数以0作为引用中的整数开始,并以不同的参数并行调用run函数:(1)在调用之间加1并延迟0.2s; (2)在呼叫之间增加10并延迟0.1秒。

运行此程序的输出是:

add 10: x is now 10
add 1: x is now 11
add 10: x is now 21
add 10: x is now 31
add 1: x is now 32
add 10: x is now 42
add 10: x is now 52
add 1: x is now 53
add 10: x is now 63
add 10: x is now 73
add 1: x is now 74
add 10: x is now 84
add 10: x is now 94
add 1: x is now 95
[...]

很容易看出引用的内容在线程之间共享。

我在第一个run函数执行数值计算而另一个run函数正在绘制第一个线程中计算的数字的情况下使用了这种结构。它运作良好。在那种情况下,其中一个线程正在写入状态,但另一个线程只是在读取。在上面编码的示例中,两个线程都写入了我认为可能有问题的状态,但我并不确切知道原因。

我的问题

我对此的疑问是:

(1)这种结构不安全吗?如果是这样,你有一个可能出错的例子吗? (2)你知道一个更好的建筑来实现同样的目标吗?我应该使用某种锁或信号吗?

提前感谢您的帮助!

一切顺利, Surikator。

1 个答案:

答案 0 :(得分:3)

这不安全。你有竞争条件,代码因你的特定时间而有效,因为add函数依赖于调度程序的实现方式(可能会发生变化)。

您需要记住的是,从概念上讲,您的线程可以随时暂停。假设线程在访问!x之后,或在计算(!x+1)之后或在赋值与printf之间被暂停。如果另一个线程进入并执行整个函数,则允许暂停的函数继续执行,结果将是错误的。

显示问题的一种方法是通过以下方式替换add函数:

let add i x =
    x := !x + i;
    for i = 1 to Random.int 1000 do ignore (Unix.getaddrinfo "localhost" "" []) done;
    Printf.printf "add %d: x is now %d\n" i !x;
    flush stdout

(一种更简单的方法是在分配和printf之间插入一个随机Thread.delay,但是上面你可以看到“常规”计算会带来问题)

这是我机器上的结果:

> ./test.native 
add 10: x is now 11
add 1: x is now 11
add 10: x is now 21
add 1: x is now 32
add 10: x is now 32
add 1: x is now 43

你需要确保添加,分配和printf由一个线程“原子地”执行(“原子地”在某种意义上说,如果在这个代码体中暂停的线程没有人应该是允许进入它)。一种做法 那就是使用互斥锁:

let add =
  let m = Mutex.create () in
  fun i x ->
    Mutex.lock m;
    try
      x := !x + i;
      for i = 1 to Random.int 1000 do ignore (Unix.getaddrinfo "localhost" "" []) done;
      Printf.printf "add %d: x is now %d\n" i !x;
      flush stdout;
      Mutex.unlock m;
    with e -> Mutex.unlock m; raise e

请注意,这适用于您提供的示例,但它假定只有add修改x。有关该主题的更多信息,我建议您阅读:

http://ocamlunix.forge.ocamlcore.org/threads.html

,特别是:

http://ocamlunix.forge.ocamlcore.org/threads.html#htoc64