gpt4 book ai didi

concurrency - Clojure 中的 `ensure` 函数在 `let` 中行为不端?

转载 作者:行者123 更新时间:2023-12-04 03:05:41 24 4
gpt4 key购买 nike

我想知道为什么以下 2 个调用的行为会有所不同,具体取决于 ensure 函数是在 let 内部还是外部引入:

=> "inside let"
(def account (ref 1000))
(def secured (ref false))
(def started (promise))
=> #'user/account
=> #'user/secured
=> #'user/started
(defn withdraw [account amount secured]
(dosync
(let [secured-value (ensure secured)]
(deliver started true)
(Thread/sleep 5000)
(println :started)
(when-not secured-value
(alter account - amount))
(println :finished))))
=> #'user/withdraw
(future (withdraw account 500 secured))
@started
(dosync (ref-set secured true))
=> #<core$future_call$reify__6320@7fbde8ed: :pending>
=> true
:started
:finished
=> true
@account
=> 500

========

    => "outside let"
(def account (ref 1000))
(def secured (ref false))
(def started (promise))
=> #'user/account
=> #'user/secured
=> #'user/started
(defn withdraw [account amount secured]
(dosync
(let [secured-value @secured]
(deliver started true)
(Thread/sleep 5000)
(println :started)
(when-not (ensure secured)
(alter account - amount))
(println :finished))))
=> #'user/withdraw
(future (withdraw account 500 secured))
@started
(dosync (ref-set secured true))
=> #<core$future_call$reify__6320@6adadff8: :pending>
=> true
=> true
:started
:started
:finished
@account
=> 1000

这里的预期语义是当 secured 设置为 true 时,应该不能提取任何钱。

我的理解是 ensure 函数将确保 secured ref 在交易的时间跨度内没有改变,所以交易重启的第二种行为似乎是合理的,但为什么它在第一种情况下表现不同?

更新:在没有 Tread/sleep 的情况下尝试:

(def account (ref 1000))
(def secured (ref false))
(def started (promise))
=> #'user/account
=> #'user/secured
=> #'user/started
(defn withdraw [account amount secured]
(dosync
(let [secured-value (ensure secured)]
(deliver started true)
;(Thread/sleep 5000)
(println :started)
(when-not secured-value
(alter account - amount))
(println :finished))))
=> #'user/withdraw
@account
=> 1000
(future (withdraw account 500 secured))
@started
(dosync (ref-set secured true))
=> #<core$future_call$reify__6320@6bce0fbf: :pending>
:started
:finished
=> true
=> true
@account
=> 500

通过对 ref-set 进行更多实验性调试

(def account (ref 1000))
(def secured (ref false))
(def started (promise))
=> #'user/account
=> #'user/secured
=> #'user/started
(defn withdraw [account amount secured]
(dosync
(let [secured-value (ensure secured)]
(deliver started true)
(Thread/sleep 5000)
(println :started)
(when-not secured-value
(alter account - amount))
(println :finished))))
=> #'user/withdraw
(future (withdraw account 500 secured))
@started
(dosync do ((println "change started") (ref-set secured true) (println "change done.")))
=> #<core$future_call$reify__6320@5b60c101: :pending>
=> true
change started
...
change started
change started
:started
:finished
change done.
NullPointerException user/eval2176/fn--2177 (form-init3061788549693294520.clj:3)
@account
=> 500

最佳答案

首先,我要重申你的问题(以确保我们在同一页上):

Due to the concurrent (ref-set secured true) call, I expect the withdraw transaction to fail (and restart) in both cases—but I only observe a restart in the non-let case. Why???

这是由于 Clojure 中 STM 的一些实现细节所致;具体来说,Refs 是使用读者/作者锁来保护的。

在您的第一个示例中(使用 let),您调用 (ensure secured) before 您调用 线程/ sleep 。由于 ensure 获取了目标 ref 的读锁,这意味着您的 ref 在整个 5 秒 sleep 延迟期间都处于只读状态。由于您的并发 (ref-set secured true) 需要对 secured 进行写锁定才能完成,因此该交易会延迟到 withdraw 交易完成完毕。这就是在这种情况下您没有观察到重启的原因——STM 实现中的内部锁强制写入事务等待读取事务完成。

相比之下,在您的第二个示例中,您调用 (ensure secured) after 您调用 Thread/sleep .这意味着在您的 5 秒 sleep 延迟之后之前,事务不知道它需要一个一致的 secured ref 值。由于事务没有做任何事情来保护 secured 的值(即,它没有锁定它),这意味着任何其他事务都可以自由修改 secured 的值> 在 ensure 调用之前的 5 秒延迟期间。在 (ensure secured) 调用之后,事务被告知它需要一个一致的 secured 引用值。在您的示例中,并发 ref-set 调用更改了该值,因此 withdraw 事务必须重新开始。

关于concurrency - Clojure 中的 `ensure` 函数在 `let` 中行为不端?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37368541/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com