我可以实时重新定义功能而不会产生副作用吗?定义线程安全吗?
答案 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”,这样它就会保持一致,直到绑定形式结束并进入任何从该绑定表单调用。
vars足以保存您的功能,并在开发代码时重新定义它们的内容。使用代码在使用vars的已部署系统上非常快速地自动重新定义函数将不太明智。不是因为vars不是线程安全的,而是因为在这种情况下vars是一个错误的可变结构来保存你的函数。 Clojure对于每个用例都有可变的结构,并且在快速自动编辑需要通过事务运行保持一致的函数的情况下,您最好在refs中保存函数。还有哪种语言可以让您选择保存功能的结构!*
答案 1 :(得分:10)
是的,它是线程安全的....但它确实有副作用。因此,根据您的尝试,您可能会得到意想不到的结果。
实质上,现有函数的defn将重新绑定命名空间中的相应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