当我在Clojure中从多个线程中定义一个变量时会发生什么?

时间:2018-03-15 08:52:54

标签: multithreading clojure

当我做类似的事情时:

(def x 123)
(future (def x 456))

第二个线程中的def最终修改主线程中的值。我明白这不是惯用的,我应该使用原子或更复杂的东西。然而,除此之外,这与我的期望相反,因为我在不同的地方读过变量是“动态的”或“线程本地的”。

那么,到底发生了什么?第二个线程是否进行了不安全的分配,类似于在C中执行等效操作时会发生什么?如果是这样,clojure是否“允许”其他不安全操作的可能性,例如从多个线程追加到列表并最终导致数据结构不一致?

2 个答案:

答案 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,或使用refatom之类的可变状态。

  

如果是这样,clojure是否“允许”其他不安全操作的可能性,例如从多个线程追加到列表并最终导致数据结构不一致?

不使用其持久性数据结构,这些数据结构在概念上是 copy-on-write (尽管副本共享效率的常识)。在Clojure和其他函数语言中编写多线程代码时,这会带来很多好处。当您附加到(持久性)列表数据结构时,您不会就地修改结构;你随着你的改变得到了一个新的结构“副本”。你如何处理这个新值,大概是把它放在像var,ref,atom等一些全局“桶”中,决定了变化的“安全性”或原子性。

可以从多个线程中轻松地修改Java的一个线程不安全的数据结构,最终处于一个不好的地方。

答案 1 :(得分:1)

可以使用alter-var-root

来完成对vars根绑定的线程安全修改
(do (future (Thread/sleep 10000) (alter-var-root #'v inc)) (def v 2))

使用相同名称多次调用def只会覆盖最后一次调用获胜的根绑定。

然而,在惯用语中,Clojure def仅用于顶级(除了宏之类的例外)。