替代版本的交换!还返回换出的值

时间:2013-03-15 20:34:52

标签: multithreading concurrency clojure atomic

我今天在IRC的 #clojure 频道上谈了这个,但是我想在这里详细介绍一下。基本上,为了更好地理解原子,swap!deref和Clojure并发作为一个整体,我想尝试编写一个函数,它不仅返回使用{交换的值 - {1}},也是换出的值。

swap!

可以打印:

(def foo (atom 42))

.
.
.

((fn [a]
  (do
    (println "swapped out: " @a)
    (println "swapped in: "(swap! a rand-int)))) foo)

但是,如果另一个帖子在swapped out: 42 swapped in: 14 swap!@a的调用之间deref相同的原子,那么我可能会交换一个不是42的值。

如何编写一个能正确回放两个值(换出和换入)的函数?

我不关心原子确实改变的各种值:我想知道的是所交换的值是什么。

这可以使用保证不会死锁的代码编写,如果是这样,为什么?

6 个答案:

答案 0 :(得分:13)

Clojure的swap!只是一个旋转的比较和设定。您可以定义一个可以返回任何内容的备用版本:

(defn alternate-swap [atom f & args]
  (loop []
    (let [old @atom
          new (apply f old args)]
      (if (compare-and-set! atom old new)
        [old new]  ; return value
        (recur)))))

答案 1 :(得分:2)

原子是不协调的,因此在交换功能之外的任何尝试都可能会失败。你可以写一个你调用的函数而不是swap!它构造了一个在应用实函数之前保存现有值的函数,然后将这个构造函数传递给swap!

user> (def foo (atom  []))
#'user/foo
user> (defn save-n-swap! [a f & args] 
         (swap! a (fn [old-val] 
                    (let [new-val (apply f (cons old-val args))] 
                       (println "swapped out: " old-val "\n" "swapped in: " new-val) 
                       new-val)))) 
#'user/save-n-swap!
user> (save-n-swap! foo conj 4) 
swapped out:  [] 
 swapped in:  [4]
[4] 
user> (save-n-swap! foo conj 4) 
swapped out:  [4]
 swapped in:  [4 4]
[4 4] 

此示例打印它,将它们推送到存储在另一个原子

中的更改日志也是有意义的

答案 2 :(得分:1)

如果你想要返回值,Stuart的答案是正确的,但是如果你打算做一堆println来理解原子/ refs是如何工作的,我会建议给atom / ref添加一个watch { {3}}

(add-watch your-atom :debug (fn [_ _ old new] (println "out" old "new" new)))

答案 3 :(得分:1)

你可以使用像:

这样的宏
(defmacro swap!-> [atom & args]
  `(let [old-val# (atom nil)
         new-val# (swap! ~atom #(do
                                  (swap! old-val# (constantly %))
                                  (-> % ~args)))]
     {:old @old-val# :new new-val#}))

(def data (atom {}))

(swap!-> data assoc :a 3001)
=> {:new {:a 3001} :old {}}

答案 4 :(得分:1)

参考交换值!自 1.9 起可用:https://clojuredocs.org/clojure.core/swap-vals%21

答案 5 :(得分:0)

您可以依赖承诺将当前值存储在swap!操作中。然后在向量中返回新旧值,如下所示:

(defn- swap-and-return-old-value!
  [^clojure.lang.IAtom atom f & args]
  (let [old-value-promise (promise)
        new-value (swap! atom
                     (fn [old-value]
                       (deliver old-value-promise old-value)
                       (apply f old-value args)))]
    [new-value @old-value-promise]))