一致读取多个refs

时间:2013-11-19 10:28:56

标签: clojure

我想知道我是否只能在事务中获得一致的读取。以下是一些用于说明问题的代码:

(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显示了一张幻灯片,暗示有一些方法可以在不将感知置于交易中的情况下完成,但我无法弄清楚如何。

1 个答案:

答案 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的事务内值。)