根据 Clojure 官方文档:
由于另一个线程可能在干预时间内更改了该值,因此它 [
swap!] 可能必须重试,并且在自旋循环中执行此操作。
swap!这是否意味着如果相关原子永远不会返回到读取的值,则执行交换的线程可能会永远卡在内部swap!?
是的。如果你有一个非常慢的突变与大量快速操作竞争,那么慢速操作将不得不每次重试,并且直到所有快速操作完成后才会完成。如果快速操作无限期地进行下去,那么慢速操作将永远不会完成。
例如,尝试:
(time
(let [a (atom 0)]
(future (dotimes [_ 1e9] (swap! a inc)))
(swap! a (fn [x] (Thread/sleep 1000) (* 2 x)))))
Run Code Online (Sandbox Code Playgroud)
首先,你会发现它需要很长时间才能完成,比一秒钟长得多。这是因为swap!在较小的任务全部完成之前,循环外部无法取得任何进展。您还会看到得到的答案恰好是 2000000000,这意味着加倍操作肯定是在每次增量之后最后发生的。如果增量更多,它们都会获得“优先权”。
我还想到了一些可爱的方法来使原子永远陷入僵局,而根本不占用任何更多的线程!
一种方法是让线程与自身竞争:
(let [a (atom 0)]
(swap! a (fn [x]
(swap! a inc')
(inc' x))))
Run Code Online (Sandbox Code Playgroud)
我使用inc'这样它真的是永远的:之后它不会破裂Long/MAX_VALUE。
而且这种方式甚至不涉及其他swap!操作,更不用说另一个线程了!
(swap! (atom (repeat 1)) rest)
Run Code Online (Sandbox Code Playgroud)
这里的问题是.equals比较和交换中的比较永远不会终止,因为(repeat 1)会永远持续下去。