我想知道我是否只能在事务中获得一致的读取。以下是一些用于说明问题的代码:
(def foo (ref 0))
(def bar (ref 0))
(defn incer [] (dosync (alter foo inc) (alter bar inc)))
(.start (Thread. (fn [] (last (repeatedly incer))))) ;; create a lot of action
现在我要打印foo和bar的值
(println @foo @bar)
;=> 328498765 328498766
我知道我可以使用ensure
获得一致的值(dosync (ensure foo) (ensure bar) (println @foo @bar))
;=> 356117587 356117587
我想知道这是否是唯一的方法,或者是否有更好的解决方案。在他的演讲中“我们还在吗?” (http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey)at min 55 Rich显示了一张幻灯片,暗示有一些方法可以在不将感知置于交易中的情况下完成,但我无法弄清楚如何。
答案 0 :(得分:8)
您确实需要一个事务来获得一致的读取,但您不需要ensure
:
;; will print consistent values
(dosync (println @foo @bar))
事务中的Ref读取操作就像对整个系统状态的快照执行一样。如果无法获得此意义上的一致值,则重试该事务。存在一种历史机制,其存储每个Ref的多个先前值 - 在每个Ref基础上可以配置的数字(参见(doc ref)
),默认情况是每个Ref开始不存储任何历史记录,每次参与失败的快照场景时,都会添加一个新条目,最多不超过10个。
ensure
的目的是防止写入偏斜,同时允许比(ref-set some-ref @some-ref)
更多的并发性。要了解写歪斜,请参阅Snapshot isolation上的Wikipedia页面。作为简短的总结,当两个或多个事务读取重叠的Refs集并写入非重叠的Refs集时,可能会发生异常:
(def foo (ref 0))
(def bar (ref 0))
;; timeouts added to the transaction bodies
;; to make the demonstration reliable:
(future (dosync
(let [f @foo
b @bar]
(Thread/sleep 1000)
(if (zero? (+ f b))
(alter foo dec)))))
(future (dosync
(let [f @foo
b @bar]
(Thread/sleep 1000)
(if (zero? (+ f b))
(alter bar dec)))))
(println @foo @bar)
这两个事务都可以在事务提交之前读取两个Refs并愉快地继续递减Refs的值,而显然任何事务的串行执行只会减少一个值。将@bar
更改为(ensure bar)
和@foo
更改为(ensure foo)
将排除此方案。
(实际上(ensure bar)
仅在不修改bar
的交易中需要,而(ensure foo)
仅在不修改foo
的交易中需要。{ ensure
非常有用,可以用来代替deref
/ @
,因为(ensure foo)
会返回foo
的事务内值。)