我很想知道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变化。有人知道这种行为的原因吗?
答案 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
阅读参考文献的时候,t1
和t2
将完成对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。