Is defn thread-safe?

后端 未结 2 1243
梦毁少年i
梦毁少年i 2021-01-04 14:02

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

相关标签:
2条回答
  • 2021-01-04 14:33

    Yes, it's thread safe.... but it does have side effects. Hence you may get unexpected results depending on what you are trying to do.

    In essence, defn on an existing function will rebind the corresponding var in the namespace.

    This means that:

    • Future accesses to the var will get the new version of the function
    • Existing copies of the old function that were previously read from the var will not change

    As long as you understand and are comfortable with that - you should be OK.

    EDIT: In response to Arthur's comment, here's an example:

    ; 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
    
    0 讨论(0)
  • 2021-01-04 14:35

    "thread safe enough for development, not for use in production."

    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 is a macro that resolves to somthing like

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

    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

    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.


    single thread no race

    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
    

    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
    

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

    user=> (bar)
    6
    user=> 
    

    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
    

    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.


    how to use vars with no race condition

    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
    

    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.


    Clojure gives you choices, but you must choose

    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!*

    • not a real question, just about any functional language can do this
    0 讨论(0)
提交回复
热议问题