defn 是线程安全的吗? [英] Is defn thread-safe?

查看:23
本文介绍了defn 是线程安全的吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我可以在没有副作用的情况下实时重新定义函数吗?defn 线程安全吗?

Can I redefine function in real-time without side effects? Is defn thread-safe?

推荐答案

线程对于开发来说足够安全,不适用于生产."

使用 defn 重新定义函数可能会破坏调用它的函数,如果它们在调用更改时正在运行.开发中没问题,因为你可以在它中断后重新启动.如果你能控制你正在改变的函数什么时候被调用就足够安全了.

using defn to redefine functions can break functions that call it if they are running while the call changes. it's ok in development because you can just restart after it breaks. It's safe enough if you can control when the function you are changing is called.

defn 是一个解析为类似

(def name (fn [args] (code-here)))

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

so it creates an instance of a function and then puts it into the root binding of a var. vars are a mutable data structure to allow for per-thread values. so when you call defn that assigns the base value that all threads will see. if another thread then changed the var to point at some other function it would change it's copy with out affecting any other threads. all the old threads would still see the old copy

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

When you re-bind the root value of a var by calling def again (through the defn macro) you change the value that every thread which has not set it's own value will see. threads that have decided to set their own values will continue to see the value they themselves set and not have to worry about the value being changed out from under them.

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

When a function call is made the current value of the var with the name of the function, as seen by the thread doing the calling (this is important), is used. so if the value of the var changes then all future calls will see the new value; but they will only see changes to the root binding or their own thread-local binding. so first the normal case with only a root binding:

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

two threads, still no race

then we run another thread and in that thread redefine foo to return 12 instead

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

foo 的值(如 bar 所见)在第一个线程(运行 repl 的线程)中仍然保持不变

the value of foo (as seen by bar) is still unchanged in the first thread (the one running the repl)

user=> (bar)
6
user=> 

<小时>

两个线程和一个竞争条件

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


two threads and a race condition

next we will change the value of the root binding out from under a thread with no local binding and see that the value of the function foo changes half way through a function running in another thread:

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 的更改(间接调用)改变了参数的数量,这将是崩溃而不是错误的答案(可以说是更好).此时很明显,如果我们想使用 vars 和 devn 来更改我们的函数,需要做一些事情.

If the change to foo (which is called indirectly) had changed the number of arguments this would have been a crash instead of a wrong answer (which is arguably better). At this point it is fairly clear that somthing needs to be done if we want to use vars and devn for changing our functions.

您确实可能希望函数在调用中不改变,因此您可以使用线程本地绑定来保护您自己,方法是更改​​在新线程中运行的函数以将 foo 的当前值保存到其线程本地绑定中:

You really may want functions to not change mid call so you can use a thread-local binding to protect your self from this by changing the function running in the new thread to save the current value of foo into its thread-local bindings:

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)) 这可以读作将线程局部值分配给当前值的 foofoo" 这样它会保持一致,直到绑定形式结束并进入从该绑定形式调用的任何内容中.

The magic is in the expression (binding [foo foo] (code-that-uses-foo)) this could be read as "assign a thread local value to foo of the current value of foo" that way it stays consistent until the end of the binding form and into anything that is called from that binding form.

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

vars are good enough to hold your functions and redefine them to your hearts content while developing code. using code to automatically redefine functions very quickly on a deployed system using vars would be less wise. Not because vars are not thread safe, but because in this context vars are the wrong mutable structure to hold your function. Clojure has mutable structure for every use case and in the case of rapid automated editing of functions that need to stay consistent through the running of a transaction you would do better to hold you're functions in refs. What other language lets you choose the structure that holds your functions!*

  • 不是一个真正的问题,几乎任何函数式语言都可以做到这一点

这篇关于defn 是线程安全的吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆