如何在Clojure中分裂一个原子?

3
我有一个集合,存储在原子中,如下所示:

我有一个存储在原子中的集合,就像这样:

(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! 之间发生变化。有没有一种方法可以原子地执行此操作?


一般来说,让你的代码执行逻辑(例如跨内容拆分)是不纯的(任何知道引用类型而不仅仅处理数据的代码肯定是不纯的),这是一种不好的迹象。 - Charles Duffy
3个回答

6
swap! 应用的函数不需要是原子性的。你可以使用 swap! 来完成这个操作。
如果你只想保留奇数并返回偶数,你可以让 swap! 应用的函数只返回奇数,然后 atom 的值将会被更新为奇数。
剩下的问题是如何取回偶数。有很多方法可以做到这一点——我不确定哪种方法最好。也许你可以使用集合的元数据?像这样:
(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))

如果numbers保持初始值,调用(split-off-evens numbers)将返回#{2 4}。既然这仍然是atom的正确使用方式,你可以假设Clojure处理所有的线程安全问题。


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

编辑

如果您真的想使用 atoms,则可以使用以下方法:

(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))))

当您需要读取-修改-写入语义时,引用也是适当的。但是它也可以使用原子完成。请参见更新的帖子。 - justncon
是的,引用可以为您提供一种安全的方式来执行读取-修改-写入操作,但这并不意味着当现有的原子操作可以很好地完成工作时,您应该使用它们。我更喜欢您的新解决方案。您基本上只是实现了一个自定义的swap!。我认为我可能更喜欢这种方法而不是使用集合元数据。 - DaoWen
我喜欢你的解决方案;使用元数据来进行创新思考是一个不错的方式。 :) - justncon
有趣的比较和设置!我喜欢这个,但是你不会陷入无限循环吗?另外,谢谢,我需要更深入地了解 refs。 - Devon Peticolas
@DaoWen - 没错。我认为这很显然。我应该用不同的措辞来表达“你不会得到无限循环”的意思,抱歉没有指出来。 - justncon
显示剩余3条评论

0

2021年起(自1.9版本开始),您可以使用swap-vals!,它会原子性地返回旧值和新值,因此偶数仅是它们之间的差异:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接