在 The Joy of Clojure 第 11 章中理解 stress-ref

Understanding stress-ref in The Joy of Clojure Chapter 11

我无法完全理解 The Joy of Clojure 11.2.5 中“stress-ref”的行为。我的问题是,为什么读取 r 需要大量的历史记录?

背景:

对于初学者,阅读 multiversion concurrency control Clojure 软件事务内存(STM;引用类型)的底层机制可能会有帮助。

简而言之答案:

在事务的上下文中,一致性至关重要:对于从中读取的任何给定引用,重要的是只有 单个 值与该引用关联持续时间的交易。因此,重要的是在事务开始时参考的最新值在整个事务中保持可用。这就是为什么历史很重要。如果没有历史记录,在读取事务运行时对 ref 的任何更改都将导致重试,因为 ref 的起始值将不再可用。 有了 历史,我们可以放宽一点,而不是在整个交易过程中都需要 最近 ref 的值,在整个交易过程中,我们至少可以接受 一致的 值。

一些示例:

JOC 中提供的玩具示例并没有真正说明这种重要性,但希望这将有助于说明问题:

(defn stress-ref [r]
  (let [slow-tries (atom 0)]
    ;One long-running transaction
    (future
      (dosync
        (swap! slow-tries inc)
        (println "1st r read:" @r)  ; do something with the ref
        (Thread/sleep 200)          ; do some work
        (println "2nd r read:" @r)) ; do something else with the ref
      (println (format "transaction complete. r is: %s, history: %d, after: %d tries"
                       @r (.getHistoryCount r) @slow-tries)))
    ; 500 very quick transactions
    (dotimes [i 500]
      (Thread/sleep 10)
      (dosync (alter r inc)))
    :done))

(stress-ref (ref 0 :min-history 20 :max-history 30))

这个returns:

1st r read: 0
2nd r read: 0
transaction complete. r is: 19, history: 19, after: 1 tries
:done

可以看到,ref在整个交易过程中的值为0,如果这个值在整个交易过程中变化就比较奇怪了

然而,一旦交易完成,该值已经增加到 19。由于这是在“第二次读取”之后立即发生的,我们可以将其作为在整个交易过程中使用参考历史记录的证据这样我们就有了一致性。

更深入地了解交易的生命周期:

为了更深入地了解整个交易过程中发生的事情,我们可以调整我们的最小历史记录,以便我们强制重试几次:

user=>     (stress-ref (ref 0 :min-history 15 :max-history 30))
1st r read: 0
1st r read: 19
1st r read: 39
1st r read: 59
1st r read: 79
2nd r read 79
transaction complete. r is: 99, history: 19, after: 5 tries

请注意,在这种情况下,历史不足以启动。第一次尝试“第二次读取”时,事务将重新启动,因为它没有足够的历史记录来保留事务开始时它所具有的 ref 的值。与其继续处理此值的不一致,不如使用更长的历史重新启动事务。该历史记录仍然不够长,因此它再次重新启动,(等等)直到历史记录增加到足以在整个交易中具有相同的值。到时候,二读就可以顺利完成,王国就可以欢欣鼓舞了。

有些不同:

你可以说 "But why don't we just locally cache the value of r that we started off with? Then we wouldn't have to worry about all this history nonsense." 在某种程度上是这样,但那样我们就不会再做 MCC(正确的)了;我们会做一些其他形式的并发控制。我 猜测 不这样做的主要优点是实施复杂性。另一个虽然可能是使用历史可以让你在 ref 的变化超过你在交易过程中理想情况下的变化时强制重试。因此,需要权衡取舍。