"Clojure编程"(Emerick,O'Reilly)指出:
(...)如果一个新值如果从当前事务开始以来由另一个事务提交,则不能提供在事务开始时的ref的新值.有帮助的是,STM注意到了这个问题并维护了事务中涉及的ref状态的有界历史,其中历史的大小随每次重试而增加.这增加了 - 在某些时候 - 交易将不再需要重试的机会,因为,当ref被更新地更新时,期望的值仍然存在于历史中.
接下来,他们提供一些代码示例来说明问题.
首先,说明只有在完成所有编写器事务之后,读取事务才会成功(因此a = 500):
(def a (ref 0))
(future (dotimes [_ 500] (dosync (Thread/sleep 20) (alter a inc))))
@(future (dosync (Thread/sleep 1000) @a))
; 500
(ref-history-count a)
; 10
Run Code Online (Sandbox Code Playgroud)
第二,说明设置:min-history并:max-history可以帮助读取器事务重试(此时a已成功读取 - 值33):
(def a (ref 0 :min-history 50 :max-history :100))
(future (dotimes [_ 500] (dosync (Thread/sleep 20) (alter a inc))))
@(future (dosync (Thread/sleep 1000) @a))
; 33
Run Code Online (Sandbox Code Playgroud)
我确实理解为什么deref读者内部事务导致它重试(当某些编写器事务正在提交对ref的更改时).我不明白的是这一部分:"这增加了 - 在某些时候 - 交易不再需要重试的机会,因为,当ref得到最新更新时,所需的值仍然存在于历史中".
什么是"期望值"?在上面的例子中,ref历史如何随时间变化?有人可以给我一个解释或一些示例,时间表显示ref历史是如何工作的吗?
A. *_*ebb 13
Clojure的STM并不关心现在.到观察时,现在已经移动了.Clojure的STM只关心捕获状态的一致快照.
这个示例并不是很明显,因为我们知道单个读取始终是一致的快照.但是,如果你只使用dosync一个ref,那么你可能根本不应该使用refs,而是atoms.
所以,想象一下,我们正在读取a a和a b并试图返回它们的总和.当我们归还总和时,我们a并不在乎并且b是最新的 - 试图跟上现在是徒劳的.所有我们看好的是,a和b从时间一致时期.
如果在一个dosync块中,我们读到a,然后b,但b在两者之间的读取进行了更新,我们有一个a和b从不一致个时刻.我们必须再次尝试-从头再来,并尝试读取a,然后b从附近的存在.
除非......假设我们保留了b每一次改变的历史b.和以前一样,假设我们阅读a然后在完成之前发生b更新b.既然我们已保存的历史b,我们可以回去的时间之前b发生变化,找到一个一致的a和b.然后,通过一致a且b从近期开始,我们可以返回一致的总和.我们不必使用近期存在的新值重试(并且可能再次失败).
通过将进入dosync时拍摄的快照与退出时的快照进行比较来维持一致性.在此模型下,对其间相关数据的任何更改都需要重试.默认情况是乐观的情况.当发生故障时,它会在适用时标记,ref因此下次更改时会保留历史记录.现在,只要在退出时拍摄的快照与退出时的快照进行比较或保留单个过去的历史记录,就会保持一致性.因此,现在对此ref期间的单一更改dosync不会导致失败.两个变化仍然会因为历史将耗尽.如果确实发生了另一个故障,则再次标记该故障,并且现在保持长度为2的历史记录.
通过该示例,假装我们正在尝试协调多个引用.默认初始历史记录长度为0,最大值为10.
(defn stm-experiment
[min-hist max-hist]
(let [a (ref 0 :min-history min-hist :max-history max-hist)]
(future (dotimes [_ 500] (dosync (Thread/sleep 20) (alter a inc))))
(dosync (Thread/sleep 1000) @a)))
Run Code Online (Sandbox Code Playgroud)
所以默认是
(stm-experiment 0 10)
;=> 500 (probably)
Run Code Online (Sandbox Code Playgroud)
更新a每20毫秒发生一次,读取在1000毫秒后发生.因此,a每次尝试读取之前将发生50次更新.min-history和max-history的默认调整是乐观地发生0次更新a,最多10次更新.也就是说,我们从没有历史记录开始a,每次发生故障时,我们都会延长一个历史记录a,但最多只有10个.由于发生了50次更新,这将永远不够.
相比于
(stm-experiment 50 100)
;=> 0 (quite possibly, multicore)
Run Code Online (Sandbox Code Playgroud)
历史记录为50,所有50个更改a都保存在历史记录中,因此a我们在进入时捕获的状态仍然在退出时的历史队列的最后.
试试吧
(stm-experiment 48 100)
;=> 100 (or thereabouts, multicore)
Run Code Online (Sandbox Code Playgroud)
初始历史长度为48时,50次更改a将导致历史记录耗尽和读取错误.但是,这个读取错误会将历史延长到49.这仍然是不够的,所以另一个读取错误发生并且历史延长到50个.现在a一致a的开头dosync可以在历史和成功中找到在两次尝试之后发生a更新50 x 2 = 100时间.
最后,
(stm-experiment 48 48)
;=> 500
Run Code Online (Sandbox Code Playgroud)
历史长度上限为48,我们永远无法a在50次更新发现之前找到我们开始的价值.