当我做类似的事情时:
(def x 123)
(future (def x 456))
第二个线程中的def
最终修改主线程中的值。我明白这不是惯用的,我应该使用原子或更复杂的东西。然而,除此之外,这与我的期望相反,因为我在不同的地方读过变量是“动态的”或“线程本地的”。
那么,到底发生了什么?第二个线程是否进行了不安全的分配,类似于在C中执行等效操作时会发生什么?如果是这样,clojure是否“允许”其他不安全操作的可能性,例如从多个线程追加到列表并最终导致数据结构不一致?
答案 0 :(得分:5)
首先,def
是Clojure中的特殊形式,值得一读。
我在不同的地方读过变量是“动态的”或“线程本地的”。
它们可以,但这不是典型用法。从指南:
def
始终适用于根绑定,即使var在调用def的位置是线程绑定的。
为了证明这一点:
(def ^:dynamic foo 1)
(binding [foo 2] ;; thread-local binding for foo
(prn foo) ;; "2"
(def foo 3) ;; re-defs global foo var
(prn foo)) ;; "2" (still thread-local binding value)
(prn foo) ;; "3" (now refers to replaced global var)
有多个线程:
(def ^:dynamic foo 1)
(future
(Thread/sleep 500)
(prn "from other thread" foo))
(binding [foo 2]
(prn "bound, pre-def" foo)
(def foo 3)
(Thread/sleep 1000)
(prn "bound, post-def" foo))
(prn "finally" foo)
;; "bound, pre-def" 2
;; "from other thread" 3
;; "bound, post-def" 2
;; "finally" 3
那么,到底发生了什么?第二个线程是否进行了不安全的分配,类似于在C中执行等效操作时会发生什么?
取决于你对不安全的定义,但对于多个线程,它肯定是 uncoordinated和non-atomic 。您可以使用alter-var-root
以原子方式更改var,或使用ref
或atom
之类的可变状态。
如果是这样,clojure是否“允许”其他不安全操作的可能性,例如从多个线程追加到列表并最终导致数据结构不一致?
不使用其持久性数据结构,这些数据结构在概念上是 copy-on-write (尽管副本共享效率的常识)。在Clojure和其他函数语言中编写多线程代码时,这会带来很多好处。当您附加到(持久性)列表数据结构时,您不会就地修改结构;你随着你的改变得到了一个新的结构“副本”。你如何处理这个新值,大概是把它放在像var,ref,atom等一些全局“桶”中,决定了变化的“安全性”或原子性。
你可以从多个线程中轻松地修改Java的一个线程不安全的数据结构,最终处于一个不好的地方。
答案 1 :(得分:1)
可以使用alter-var-root
:
(do (future (Thread/sleep 10000) (alter-var-root #'v inc)) (def v 2))
使用相同名称多次调用def
只会覆盖最后一次调用获胜的根绑定。
然而,在惯用语中,Clojure def
仅用于顶级(除了宏之类的例外)。