一致读取多个refs

Joe*_*ann 6 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
Run Code Online (Sandbox Code Playgroud)

现在我想打印foo和bar的值

(println @foo @bar)
;=> 328498765 328498766
Run Code Online (Sandbox Code Playgroud)

我知道我可以使用ensure获得一致的值

(dosync (ensure foo) (ensure bar) (println @foo  @bar))
;=> 356117587 356117587
Run Code Online (Sandbox Code Playgroud)

我想知道这是否是唯一的方法,或者是否有更好的解决方案.在他的演讲中"我们还在吗?" (http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey)at min 55 Rich显示了一张幻灯片,暗示有一些方法可以在不将感知置于事务中的情况下进行,但是我无法弄清楚如何.

Mic*_*zyk 8

您确实需要一个事务来获得一致的读取,但您不需要ensure:

;; will print consistent values
(dosync (println @foo @bar))
Run Code Online (Sandbox Code Playgroud)

事务中的Ref读取操作就好像针对整个系统状态的快照执行一样.如果无法获得此意义上的一致值,则重试该事务.存在一种历史机制,其存储每个Ref的多个先前值 - 在每个Ref基础上可以配置的数字(参见参考资料(doc ref)),默认情况是每个Ref开始时根本不存储任何历史记录当它参与失败的快照场景时,它会添加一个新条目,最多不超过10个.

ensure的目的是防止写入偏斜,同时允许更多的并发性(ref-set some-ref @some-ref).要了解写入倾斜,请参阅快照隔离的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)
Run Code Online (Sandbox Code Playgroud)

这是可能的交易双方同时读参考文献要么事务提交之前,并愉快地继续递减两个参考文献的价值观,而明确交易的任何串行执行只会递减一个值.更改@bar(ensure bar)@foo(ensure foo)会排除这种情况.

(实际上(ensure bar)只在不修改的事务中需要,bar并且(ensure foo)只在不修改的事务中需要foo.在ensure有用的场景中,它可以用来代替deref/ @,因为它(ensure foo)返回事务中的值foo.)