请在2024 Clojure状态调查!中分享您的想法。

欢迎!请查看关于页面了解该工作的更多信息。

0
引用、代理、原子

嗨 ,

我在REPL上玩弄refs时遇到了一些奇怪的行为。
因此我有一个带有简单监视功能的ref

(defn watcher [_ r o n]
  (printf "ref: %s old: %s new: %s time: %d\n" @r o n (System/currentTimeMillis))
  (flush))

(def x (ref 2))
(add-watch x nil watcher) 

然后我连续两次评估下面的表达式

(future (dosync (ensure x) (Thread/sleep 3000) (alter x * 2)))

我预期的结果是监视函数应该连续两次打印ref的值,大约相隔3秒。
但什么都没有发生。
当我随后尝试使用其他命令(如)访问ref时

(dosync (ensure x) (alter x inc))

REPL完全锁定。一次我过了几分钟才得到预期的结果,另一次我却得到了这个错误信息

Execution error at reftest.core/eval24449 (form-init12758928819079876959.clj:34).                                               
Transaction failed after reaching retry limit

我发现令人困惑的是,当我在该let块中进行评估时,一切似乎都工作正常

(let [x (ref 2)]
  (add-watch x nil watcher)
  (println "start:" (deref x))
  (future (dosync (ensure x) (Thread/sleep 3000) (alter x * 2)))
  (future (dosync (ensure x) (Thread/sleep 3000) (alter x * 2)))
  (dosync (ensure x) (alter x inc))
  (println "end:" @x))

这种行为在REPL中是一种错误,还是我在做错什么?
我如何保证这类STM事务在真实代码中不会失败?

你可以通过在`dosync`体中添加print语句来验证Alex所写的内容。此外,`let`表示也可以挂起,如果你在调用`future`之间加入100毫秒的sleep时间。尽管这可能只是偶然发生的,而不是一个规律。

1 答案

+2

被选中
 
最佳答案

我假设两个事务都失败了并且多次重试,每次重试需要非常长的时间,直到其中一个意外地得到正确的时间来工作或者达到重试的最大次数。

你不应该在事务中执行阻塞操作。

另外,你不需要确保你在事务中是否更改了相同的ref。
你好,Alex,感谢你关于执行阻塞操作的错误警告。

我使用ensured的原因是,我发现当我将其省略时,第三个事务在将ref doubling之前的任何一个事务之前增加ref。例如,以下代码在最后打印出12
 
(let [x (ref 2)]
  (add-watch x nil watcher)
  (future (dosync (Thread/sleep 3000) (alter x * 2)))
  (future (dosync (Thread/sleep 3000) (alter x * 2)))
  (Thread/sleep 100) ; 确保其他事务在运行
  (dosync (alter x inc))
  (println "end:" (deref x) (quot (System/currentTimeMillis) 1000)))

无论如何,经过更多关于refs和STM的阅读,以及深思熟虑我的项目的业务逻辑,我得出结论,一个或两个原子可能比最初设想的数十个refs更适合我的用例。
...