让两个线程在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。
答案 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
,特别是: