定义线程安全吗?

时间:2011-03-03 13:32:26

标签: clojure

我可以实时重新定义功能而不会产生副作用吗?定义线程安全吗?

2 个答案:

答案 0 :(得分:12)

“线程安全,足以开发,而不是用于生产。”

使用defn重新定义函数可能会破坏调用它的函数,如果它们在调用更改时正在运行。它在开发中是可以的,因为你可以在它休息后重新启动。如果您可以控制何时调用您正在更改的功能,那么它就足够安全了。

defn是一个解析为

之类的宏
(def name (fn [args] (code-here)))

所以它创建了一个函数实例,然后将它放入var 的根绑定中。 vars是一个可变数据结构,允许每线程值。所以当你调用defn时,它会分配所有线程都会看到的基值。如果另一个线程然后将var更改为指向某个其他函数,它将更改它的副本而不影响任何其他线程。所有旧线程仍将看到旧副本

当您通过再次调用def重新绑定var的根值(通过defn宏)时,您将更改未设置其自身值的每个线程都将看到的值。决定设置自己的值的线程将继续看到它们自己设置的值,而不必担心从它们下面改变的值。


单线程没有比赛

当进行函数调用时,使用具有函数名称的var的当前值,如执行调用的线程所见(这很重要)。因此,如果var的值发生变化,则所有将来的调用都将看到新值;但他们只会看到根绑定或他们自己的线程本地绑定的更改。所以首先是只有根绑定的正常情况:

user=> (defn foo [] 4)
#'user/foo
user=> (defn bar [] (foo))
#'user/bar
user=> (bar)
4
user=> (defn foo [] 6)
#'user/foo
user=> (bar)
6

两个线程,仍然没有比赛

然后我们运行另一个线程并在该线程中重新定义foo以返回12而不是

user=> (.start (Thread. (fn [] (binding  [foo (fn [] 12)] (println (bar))))))
nil
user=> 12

foo的值(由bar看到)在第一个线程(运行repl的线程)中仍未改变

user=> (bar)
6
user=> 

两个线程和竞争条件

接下来我们将从没有本地绑定的线程下更改根绑定的值,并看到函数foo的值在另一个线程中运行的函数的一半变化:

user=> (.start (Thread. (fn [] (println (bar)) 
                        (Thread/sleep 20000) 
                        (println (bar)))))                        
nil
user=> 6                ;foo at the start of the function

user=> (defn foo [] 7)  ;in the middle of the 20 seond sleep we redefine foo
#'user/foo
user=> 7                ; the redefined foo is used at the end of the function

如果对foo(间接调用)的更改改变了参数的数量,那么这将是崩溃而不是错误的答案(这可能更好)。在这一点上,很明显,如果我们想使用变量和devn来改变我们的功能,那么需要做一些事情。


如何使用没有竞争条件的变种

你真的可能希望函数不改变mid调用,这样你就可以使用线程局部绑定来保护你的自我,方法是改变新线程中运行的函数,将foo的当前值保存到它的线程本地绑定中:

user=> (.start (Thread. (fn [] (binding [foo foo] (println (bar)) 
                                                  (Thread/sleep 20000)
                                                  (println (bar))))))
nil
user=> 7

user=> (defn foo [] 9)
#'user/foo
user=> 7

魔术在表达式(binding [foo foo] (code-that-uses-foo))中,这可以被解读为“将一个线程局部值赋给foo当前值的foo”,这样它就会保持一致,直到绑定形式结束并进入任何从该绑定表单调用。


Clojure为您提供选择,但您必须选择

vars足以保存您的功能,并在开发代码时重新定义它们的内容。使用代码在使用vars的已部署系统上非常快速地自动重新定义函数将不太明智。不是因为vars不是线程安全的,而是因为在这种情况下vars是一个错误的可变结构来保存你的函数。 Clojure对于每个用例都有可变的结构,并且在快速自动编辑需要通过事务运行保持一致的函数的情况下,您最好在refs中保存函数。还有哪种语言可以让您选择保存功能的结构!*

  • 不是一个真正的问题,只是任何功能语言都可以做到这一点

答案 1 :(得分:10)

是的,它是线程安全的....但它确实有副作用。因此,根据您的尝试,您可能会得到意想不到的结果。

实质上,现有函数的defn将重新绑定命名空间中的相应var

这意味着:

  • 对var的未来访问将获得函数的新版本
  • 先前从var读取的旧函数的现有副本将更改

只要你明白并且对此感到满意 - 你应该没事。

编辑:回应Arthur的评论,这是一个例子:

; original function
(defn my-func [x] (+ x 3))

; a vector that holds a copy of the original function
(def my-func-vector [my-func])

; testing it works
(my-func 2)
=> 5
((my-func-vector 0) 2)
=> 5

; now redefine the function
(defn my-func [x] (+ x 10))

; direct call to my-func uses the new version, but the vector still contains the old version....
(my-func 2)
=> 12
((my-func-vector 0) 2)
=> 5