如何在Clojure中拆分原子?

时间:2015-03-04 04:26:34

标签: clojure

我有一个存储在原子中的集合,如此

(def numbers (atom #{1 2 3 4 5}))

使用swap!定期以固定方式添加和删除数字。在一个单独的线程中,我希望有一个函数可以从集合中提取和删除偶数并返回它们。

我能做到的一种方式看起来像是:

(let [{even true odd false} (group-by even? @numbers)]
  (reset! numbers odd)
  even)

但是,这不是原子操作。 numbers可能会在group-byreset!之间发生变化。有没有办法以原子方式执行此操作?

3 个答案:

答案 0 :(得分:4)

swap!应用的函数不需要是原子的。您应该可以使用swap!执行此操作。

如果你只想留下奇数并取回偶数,你可以让swap!应用的函数只返回几率,那么原子是什么?值将更新为。

剩下的问题是如何找回偶数。有很多方法可以做到这一点 - 我不确定什么是最好的。也许你可以在你的收藏中使用元数据?像这样:

(def numbers (atom #{1 2 3 4 5}))

(defn atom-splitter [xs]
  (let [{even true odd false} (group-by even? xs)]
    (with-meta (set odd) {:evens (set even)})))

(swap! numbers atom-splitter)

@numbers的值是#{1 3 5}

(meta @numbers)的值为{:evens #{2 4}}

由于swap!返回交换的值,因此可以确保返回值的元数据包含拆分集合的另一半。例如,以下是安全的:

(defn split-off-evens [a]
  (-> (swap! numbers atom-splitter) meta :evens))

如果(split-off-evens numbers)具有原始值,则调用#{2 4}会返回numbers。由于这仍然是atom的正确使用,您可以假设Clojure处理所有线程安全问题。

答案 1 :(得分:1)

在这种情况下,保持原子性的惯用方法是使用事务:

(def numbers (ref #{1 2 3 4 5}))
(future (loop []
          (Thread/sleep 10)
          (dosync
           (alter numbers conj (rand-int 1000)))
          (recur)))

(dosync
 (let [{even true odd false} (group-by even? @numbers)]
   (ref-set numbers odd)
   even))

有关详情,请参阅:http://clojure.org/refs

修改

如果你真的想使用原子,那么这将有效:

(def numbers (atom #{1 2 3 4 5}))
(future (loop []
          (Thread/sleep 10)
          (swap! numbers conj (rand-int 1000)))
          (recur))

(loop []
  (let [val @numbers
        {even true odd false} (group-by even? val)]
    (if (compare-and-set! numbers val odd)
      even
      (recur))))

答案 2 :(得分:0)

与此同时,在 2021 年(自 1.9 起),您可以使用 swap-vals! 以原子方式返回旧值和新值,因此当然偶数只是它们之间的区别:

(apply clojure.set/difference
       (swap-vals! numbers
                   #(into #{} (filter odd? %))))