在STM交易中,refs是否真的一致?

时间:2012-04-16 17:18:13

标签: clojure

我在clojure.org/refs看了

  

所有Refs的读取都将看到“Ref world”的一致快照,作为交易的起点(其“读取点”)。交易将看到它所做的任何更改。这称为in-transaction-value。

维基百科上还有一个指向Snapshot Isolation的链接,暗示一旦交易开始,任何数量的引用的读取都会相互一致。

我做了一个测试案例......

(def r1 (ref 0))
(def r2 (ref 0))

(defn delay-then-inc-ref [id ref delay]
  (.start 
    (Thread. 
        #((println id " start")
          (Thread/sleep delay)
          (dosync
             (alter ref inc))
             (println id " end")))))

(defn deref-delay-deref [ref1 ref2 delay]
    (.start 
       (Thread. 
          #((println "S start")
            (dosync 
              (let [a @ref2]
                 (Thread/sleep delay)
                 (println "S r1=" @ref1))) ; @ref1 consistent with @ref2 ?
                 (println "S end")))))

*clojure-version*
;=> {:major 1, :minor 3, :incremental 0, :qualifier nil}
(deref-delay-deref r1 r2 2000) 
(delay-then-inc-ref "1" r1 500)
(delay-then-inc-ref "2" r1 1000)
(delay-then-inc-ref "3" r1 1500)

输出结果为:

S start
1 start
2 start
3 start
1 end
2 end
3 end
r1 = 3
S end
nil

r1 = 3而不是r1 = 0的值表明在deref-delay-deref sleep之后,<{1}}在之后选择了r1 的值>发生了三次delay-then-inc-ref次交易。

请注意,在特定交易期间,我知道ensure 阻止更新其他交易的引用,但我不认为这适用于此。只要我看到与交易开始一致的值,我就不在乎ref1是否会发生变化。

此行为如何与上述文档相符?

1 个答案:

答案 0 :(得分:1)

事实证明,如果ref有一些历史记录,它的行为与我期望的一样,所以更改ref声明以添加:min-history,然后如图所示设置两个refs,似乎可以使它工作.... / p>

(def r1 (ref 0 :min-history 5))
(def r2 (ref 0 :min-history 5))

(dosync
 (ref-set r1 0)
 (ref-set r2 0))

然后输出是:

S start
1 start
1 end
2 start
2 end
3 start
3 end
S r1= 0
S end
nil

阅读here,很明显发生了什么。读取事务正在重新启动,因为在事务启动之前,ref历史记录中没有条目。为了解决这个问题,我增加了一些日志记录:

(defn deref-delay-deref [ref1 ref2 delay]
    (.start 
       (Thread. 
          #((println "S start")
            (dosync
              (println "transaction starting")
              (let [a @ref2]
                 (Thread/sleep delay)
                 (println "S r1=" @ref1))) ; should be consistent with @ref2
            (println "S end")))))

没有历史记录模式的输出:

S start
transaction starting
1 start
2 start
3 start
1 end
2 end
3 end
transaction starting
S r1= 3
S end

并使用历史模式:

S start
transaction starting
1 start
2 start
3 start
1 end
2 end
3 end
S r1= 0
S end
nil

更新:由于测试用例的人为性质,我上面的答案结果令人分心。在实际使用中,事务是否重新启动并不重要,因为必须写入事务以使它们可重新启动。运行时不保证只读事务是否在历史存在/不存在时完成。相反,它可以做任何必要的事情来完成事务的世界,并且必须记住事务代码。更详细的讨论here

我要离开上面作为参考。