ABA与Clojure软件交易记忆

时间:2017-05-10 13:33:06

标签: clojure transactions stm aba

我很想知道Clojure是否有针对ABA问题的内置解决方案。 我正在创建一个显示此问题的示例,但Clojure会以某种方式检测到这些更改。这是因为Clojure的交易比较参考而不是价值吗?

我的例子:

(def x (ref 42))
(def io (atom false))
(def tries (atom 0))

(def t1 (Thread. (fn [] (dosync (commute x - 42)))))
(def t2 (Thread. (fn [] (dosync 
                          (Thread/sleep 100)
                          (commute x + 42)))))
(def t3 (Thread. 
  (fn [] 
    (dosync 
      (do 
        (Thread/sleep 1000) 
        (swap! tries inc) 
        (if (= 42 @x) 
          (reset! io true)))))))

(.start t3)
(.start t1)
(.start t2)

(.join t1)
@x
(.join t2)
@x
(.join t3)
@tries
(if (= true @io) (println "The answer is " @x))

尝试次数总是2,所以事务t3必须注意到t1和t2的ref变化。有人知道这种行为的原因吗?

2 个答案:

答案 0 :(得分:0)

这是正确的,这是预期的行为(虽然我希望tries为1)。除了讨论软件事务存储器(STM)的许多Clojure书籍之外,您还可以查看

此外,通常最好使用alter代替commute,这很容易出错,通常是"过早优化的情况"。

答案 1 :(得分:0)

在回答手头的问题之前,请允许我说到目前为止,关于Clojure STM的最佳信息来源 - 除了源代码本身 - 我知道的是Mark Volkmann的Software Transactional Memory文章(链接点)在更改日志页面上,点击链接到最新版本。它非常全面。 (不要担心2009年的时间戳,STM的变化不大。)如果你想仔细考虑这个场景中的工作原理,我强烈建议你阅读它。

至于手头的情景:

对于Ref的事务内读取,STM承诺返回在当前事务尝试之前提交的值。 (当然除非当前事务尝试本身设置了有问题的Ref的事务内值。)此值可能是也可能不是写入Ref的最新值,但如果不是,则读取需要为从Ref的历史中得到满足。如果Ref的历史记录不包含此值,则会为Ref和事务重试记录 fault 。随后Ref的历史链的长度可能因故障而增加,直到Ref的最大历史长度(默认为10),但请注意,这只会在有机会(另一次写入Ref)时发生,并且只会帮助开始“足够晚”的交易(以便他们的时间戳晚于历史记录中记录的某些值)。

在目前的情况下,在t3阅读参考文献的时候,t1t2将完成对x的写入而没有任何问题x将无法再满足要求t3首次尝试之前的值的读取请求。 (这是因为默认情况下Ref的历史链从0开始,意味着没有保留历史值。)因此t3必须记录x的错误并重试。

(如果您针对相同的Ref和帮助程序原子重新运行三个事务 - 比如,通过将除前三行之外的所有行再次粘贴到您的REPL中 - 您将看到tries跳转到4第二次运行,然后到第三次运行5,表示此时有历史值。)

关于ABA问题:

ABA问题与STM无关,因为在适当的ABA情况下,“B”由不同的线程写入存储器位置(1),(2)在第一次读取“A”之后由“主”线程(意味着遭受ABA问题的线程),然后类似地第二个“A”由不同的线程写入(1)和(2)在“B”写入之后,并且两个“As”由主线程观察,但“B”不是 - 但如上所述,在STM事务中,您无法在事务尝试启动后观察到由其他线程写入Ref的值,因此,如果您观察到第一个“A”,您将无法观察到“B”或第二个“A”。

这并不意味着STM不会出现与并发相关的问题 - 它很容易遇到写偏斜(在the Wikipedia article on snapshot isolation中描述 - 这就是ensure函数旨在修复的问题,但这取决于用户代码在适当的时候调用它,commute可能被滥用& c。