我有一个包裹物品矢量的原子:
(def items (atom [1 2 3 4]))
我想原子地删除第一个项目并将其返回。这段代码说明了逻辑:
(let [x (first @items)]
(swap! items #(subvec % 1))
x)
但是当许多线程相互竞争时,上面的代码是不正确的。阅读和更新之间存在竞争条件。
如this answer所述,原子用于非协调的同步访问。我希望这可以用原子而不是ref来完成,因为原子更简单。
是否存在仅使用原子而非refs的解决方案? (我将尝试使用手表,看看情况如何。)如果你的答案坚持需要参考,你能否解释为什么需要参考,即使建议在需要“协调同步访问许多身份时参考” “(与上述相同的链接)。
这与How do I update a vector element of an atom in Clojure?和Best way to remove item in a list for an atom in Clojure等其他相关问题不同,因为我想更新矢量原子并返回值。
答案 0 :(得分:11)
compareAndSet
的自旋循环用于swap!
原子。 Clojure还为原子提供了较低级别compare-and-set!
,您可以使用它来执行自己的自旋循环并返回旧值和新值。
(defn swap*!
"Like swap! but returns a vector of [old-value new-value]"
[atom f & args]
(loop []
(let [ov @atom
nv (apply f ov args)]
(if (compare-and-set! atom ov nv)
[ov nv]
(recur)))))
(defn remove-first-and-return [atom]
(let [[ov nv] (swap*! atom subvec 1)]
(first ov)))
答案 1 :(得分:4)
如果您需要使用原子,请使用本地封装的原子来存储获胜交易的交易中价值的第一个元素。
(let [f-atom (atom nil)]
(swap! items-atom #(do (reset! f-atom (first %))
(rest %)))
@f-atom)
或者,使用ref
和dosync
事务块实现相同的目标:
(dosync
(let [f (first @items-ref)]
(alter items-ref rest)
f)))
这里,如果事务因并行写入操作成功而失败,则事务不会返回或对ref有影响,直到它可以被重试为止,以便执行读取和写入操作而不会被另一个写入操作中断。
答案 2 :(得分:3)
我的首选解决方案是在所包含值的元数据中存储所需的任何返回值。我不知道它是如何惯用的,它显然只适用于实现IMeta
接口的类。
(defn pop-head!
[a]
(-> (swap! a #(with-meta (subvec % 1) {:head (% 0)}))
(meta)
:head))
这得益于swap!
返回现在存储的原子值的事实。我们来试试吧:
(def a (atom [1 2 3 4]))
(pop-head! a) ;; => 1
(pop-head! a) ;; => 2
(pop-head! a) ;; => 3
(pop-head! a) ;; => 4
(pop-head! a) ;; => IndexOutOfBoundsException...
是的,您可能想要处理这种情况。 ;)
答案 3 :(得分:1)
这不是此用例的解决方案,但可能适用于其他用户。
您可以使用add-watch在原子上创建一个手表,该手表将发送包含旧值和新值的事件。
答案 4 :(得分:0)
由于Clojure 1.9中添加了swap-vals!
,因此您可以使用(ffirst (swap-vals! items rest))
从向量中返回第一个元素,并通过一次操作更新原子。