我有一个存储在原子中的集合,如此
(def numbers (atom #{1 2 3 4 5}))
使用swap!
定期以固定方式添加和删除数字。在一个单独的线程中,我希望有一个函数可以从集合中提取和删除偶数并返回它们。
我能做到的一种方式看起来像是:
(let [{even true odd false} (group-by even? @numbers)]
(reset! numbers odd)
even)
但是,这不是原子操作。 numbers
可能会在group-by
和reset!
之间发生变化。有没有办法以原子方式执行此操作?
答案 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? %))))