开发者

Thread safety while sharing a reference between POSIX threads in OCaml

A primitive way to have two threads share a state with type int ref in OCaml is:

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

Function add just adds i to the integer in the reference x. Function run simply keeps applying f to the same argument x with delays of d between each application. The main function starts with 0 as the integer in the reference and invokes the run function in parallel with different arguments: (1) adding 1 and delaying 0.2s between calls; (2) adding 10 and delaying 0.1s between calls.

The output of running this program was:

add 10: x is now 10
add 1: x is now 11
add 10: x is now 21
add 10: x is n开发者_开发技巧ow 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
[...]

It's easy to see that the contents of the reference are shared between threads.

I have used this construction in a case where the first run function was performing numerical calculations and the other run function was plotting the numbers calculated in the first thread. It worked well. In that case one of the threads was writing to the state but the other one was only reading. In the example coded above both threads write to the state which I think can be problematic, but I don't exactly know why.

My Question

My questions about this are:

(1) Is this construction unsafe? If so, do you have an example where it can go wrong? (2) Do you know of a better construction to achieve the same goal? Should I use some kind of locks or signals?

Thanks in advance for any help!

All the best, Surikator.


It's not safe. You have a race condition, the code works because of your particular timings and because the add function relies on the way the scheduler is implemented (which is subject to change).

What you need to remember is that conceptually your thread can be suspended at any time. Suppose the thread is suspended after the access to !x, or after the computation (!x+1) or between the assignment and the printf. If the other thread chimes in and executes the whole function before the suspended one is allowed to proceed, the result will be wrong.

One way to show the problem is to replace the add function by :

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

(A simpler way is to insert a random Thread.delay between the assignement and the printf, but with the above you can see that a "regular" computation can bring in the problem)

Here's the result on my machine :

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

You need to ensure that the addition, the assignement and the printf are executed "atomically" by a thread ("atomically" in the sense that if a thread in suspended in this body of code no one should be allowed to enter it). One way of doing that is to use a mutex :

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

Note that this works for the example you give but it assumes that only add modifies x. For more on the subject I recommend you to read :

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

and in particular :

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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜