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

欢迎!请参阅关于页面以了解更多信息。

0
引用、代理、原子

你好,

我在 REPL 中的 ref 问题上遇到了一些奇怪的行.
所以我有一个具有简单监视器的 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 完全挂起。一次我获得了预期的结果几分钟 later,另一次我获得了这个错误消息

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

我觉得奇怪的是,当我评估这个 let-block 时,一切似乎都很好

(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 事务在实际代码中不会失败?

by
你可以通过在 `dosync` 结构体中添加打印语句来确认 Alex 所述的内容。此外,如果在使用 `future` 进行调用之间添加例如 100 毫秒的睡眠时间,`let` 变体也可能会挂起。尽管这可能更像是一个巧合,而不是一个规则。

1 答案

+2

选择
 
最佳答案

我假设两个事务都失败了并在多次重试中,每次重试都花费很长时间,直到其中一个成功或者达到最大重试次数。

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

此外,你不需要确保你是在事务中更改相同的引用。
你好,Alex,感谢你关于执行阻塞操作的警告。

我使用确保是因为我发现,当我不使用它时,第三个事务在之前的两个事务中的一个没有将它翻倍之前,会递增引用。例如,以下代码在末尾打印出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)))

不管怎样,在阅读了一些关于引用和STM的内容后,我更深入地考虑了我的项目的业务逻辑,我得出结论,一两个原子可能比最初想象的几十个引用更适合我的使用场景。
...