在“欢乐的第十一章”中理解压力 - 参考

时间:2015-01-08 03:27:32

标签: clojure stm

我无法完全理解`stress-ref'在Clojure的喜悦中11.2.5。我的问题是,为什么阅读r需要很长的历史?

1 个答案:

答案 0 :(得分:2)

背景:

对于初学者来说,对multiversion concurrency control进行一些阅读可能会有所帮助,这是Clojure软件事务记忆(STM;参考类型)背后的机制。

简而言之:

在事务的上下文中,一致性至关重要:对于从中读取的任何给定的ref,重要的是在该持续时间内只有单个值与该ref关联。交易。因此,在事务开始时最新的引用值仍然可用于整个事务,这一点非常重要。这就是为什么历史很重要;没有历史记录,在读取事务运行时对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))

返回:

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

正如您所看到的,整个事务中ref的值为0.如果在整个事务过程中更改了,那将是相当奇怪的。

但是,一旦事务完成,该值已经增加到19.由于这是在" 2nd read"之后立即发生的,我们可以将此作为证据,在整个交易过程中,ref历史记录正在使用,以便我们保持一致。

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

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

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值。不是继续使用此值的不一致,而是以较长的历史记录重新启动事务。该历史记录仍然不够长,因此它会重新启动,等等,直到历史记录增加到足以在整个事务中具有相同的值。在那时,第二次阅读可以成功完成,王国可以欢欣鼓舞。

不同的东西:

你可以说"但为什么我们不能在本地缓存我们开始使用的r的值?那么我们不必担心所有这些历史废话。"在某种程度上,这是真的,但那时我们将不再做MCC(正确);我们正在做一些其他形式的并发控制。我猜测不这样做的主要优点是实现复杂性。另一种情况可能是,使用历史记录可以强制重试,当裁员的变化超过您在交易过程中的理想情况时。所以,需要权衡利弊。