"Поток достаточно безопасен для разработки, но не для использования в производстве."
Использование defn для переопределения функций может нарушить функции, вызывающие его, если они выполняются во время изменения вызова.это нормально в разработке, потому что вы можете просто перезапустить его после перерыва.Достаточно безопасно, если вы можете контролировать, когда вызывается изменяемая функция.
defn
- это макрос, который разрешается примерно так:
(def name (fn [args] (code-here)))
, поэтому он создает экземпляр функцииа затем помещает его в корневую привязку var .vars - это изменяемая структура данных, позволяющая принимать значений для каждого потока .поэтому при вызове defn назначается базовое значение, которое будут видеть все потоки.если другой поток затем изменил var, чтобы он указывал на какую-то другую функцию, он изменит свою копию, не затрагивая другие потоки. все старые потоки все равно увидят старую копию
Когда вы-привязать корневое значение переменной путем повторного вызова def
(через макрос defn), вы измените значение, которое будет видеть каждый поток, который не установил свое собственное значение.Потоки, которые решили установить свои собственные значения, будут продолжать видеть значение, которое они сами устанавливают, и им не придется беспокоиться об изменении значения из-под них.
одиночный поток без гонки
Когда выполняется вызов функции, текущее значение переменной с именем функции, как видно из потока, выполняющего вызов (это важно), используется.поэтому, если значение переменной изменится, то все будущие вызовы увидят новое значение;но они увидят только изменения в корневой привязке или в собственной привязке локального потока.Итак, сначала нормальный случай только с корневой привязкой:
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 (как видно из столбца) все еще остается неизменным в первом потоке (в котором выполняется 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 (которое вызывается косвенно) изменило число аргументов, это был бы сбой, а не неправильный ответ (что, возможно, лучше).На данный момент довольно ясно, что нужно что-то сделать, если мы хотим использовать vars и devn для изменения наших функций.
как использовать переменные без условий гонки
Вына самом деле может потребоваться, чтобы функции не меняли промежуточный вызов, поэтому вы можете использовать локальную привязку потока, чтобы защитить себя от этого, изменив функцию, выполняющуюся в новом потоке, чтобы сохранить текущее значение 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 было бы менее разумным.Не потому, что переменные не являются поточно-ориентированными, а потому что в этом контексте переменные являются неправильной изменяемой структурой для хранения вашей функции .Clojure имеет изменяемую структуру для каждого варианта использования, и в случае быстрого автоматического редактирования функций, которые должны оставаться согласованными в ходе выполнения транзакции, вы бы лучше держали свои функции в ссылках.Какой другой язык позволяет вам выбрать структуру, которая содержит ваши функции! *
- не настоящий вопрос, почти любой функциональный язык может сделать это