Clojure 在STM事务中引用是否真的一致?

Clojure 在STM事务中引用是否真的一致?,clojure,stm,Clojure,Stm,我读了那本书 从事务的起点(其“读取点”)起,所有Ref读取都将看到“Ref世界”的一致快照。事务将看到它所做的任何更改。这称为事务内值 维基百科上还有一个指向的链接,这意味着一旦事务开始,对任意数量的引用的读取将彼此一致 我做了一个测试用例 (def r1 (ref 0)) (def r2 (ref 0)) (defn delay-then-inc-ref [id ref delay] (.start (Thread. #((println id " star

我读了那本书

从事务的起点(其“读取点”)起,所有Ref读取都将看到“Ref世界”的一致快照。事务将看到它所做的任何更改。这称为事务内值

维基百科上还有一个指向的链接,这意味着一旦事务开始,对任意数量的引用的读取将彼此一致

我做了一个测试用例

(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
S start
1 start
1 end
2 start
2 end
3 start
3 end
S r1= 0
S end
nil
r1=3
的值而不是
r1=0
表明,在
deref delay deref
中,在
sleep
之后的ref1的deref是在三个
delay-then inc ref
事务发生后拾取r1的值

请注意,我知道如何确保在特定事务期间防止其他事务更新引用,但我认为这不适用于这里。只要看到与交易开始时一致的值,我不在乎
ref1
是否更改


这种行为如何与上述参考文档相匹配?

事实证明,如果ref有一些历史记录,它的行为与我预期的一样,因此更改ref声明以添加一个
:min history
,然后如图所示设置两个ref,似乎可以使其正常工作

(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
2 start
3 start
1 end
2 end
3 end
r1 = 3
S end
nil
S start
1 start
1 end
2 start
2 end
3 start
3 end
S r1= 0
S end
nil
阅读,很清楚发生了什么。读取事务正在重新启动,因为事务启动之前的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")))))
不带历史记录mods的输出:

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
使用历史mods:

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
更新:事实证明,由于测试用例的人为性质,我上面的答案有些让人分心。在实际使用中,事务是否重新启动并不重要,因为事务必须以可重新启动的方式写入。运行时不保证在有/无历史记录的情况下是否完成只读事务。相反,它可以做任何必要的事情来完成事务世界,而编写事务代码时必须牢记这一点。更详细的讨论


我将以上内容留作参考。

因为我回答了自己的问题,所以根据meta.stackoverflow.com的礼仪建议,我将此答案标记为社区维基