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

欢迎!请参阅 关于 页面了解更多关于此功能的信息。

0
... 引用、代理、原子

你好,

我在使用 REPL 中的引用时遇到了一些奇怪的行为。
因此,我有一个具有简单监视器函数的引用

(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)))

我预期监视器函数应该连续打印两次引用的值,时间大约相隔3秒。
但没有发生任何事情。
当我尝试使用另一个命令访问引用时

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

REPL 完全阻塞。有一次我得到期望的结果是在几分钟之后,另一次我得到以下错误消息

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

...
你可以通过在 `dosync` 体中添加打印语句来确认 Alex 写的内容。此外,如果在调用 `future` 之间添加例如 100 毫秒的睡眠,`let` 表达形式也可能挂起。尽管这可能更多是一种偶然,而不是规则。

1 答案

+2

已选择
 
最佳答案

我假设这两个事务都失败并多次重试,每次重试都花费很长时间,直到其中一个偶然得到正确的时机工作或者达到最大重试次数。

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

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

我使用了上面的ensure是因为我发现,当我省略它时,第三个事务在两个之前的交易将其加倍之前增加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相比,一个或两个原子对我的用例可能更合适。
...