从Clojure矢量原子中删除第一个项目并将其返回

时间:2014-03-14 15:42:20

标签: clojure

我有一个包裹物品矢量的原子:

(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等其他相关问题不同,因为我想更新矢量原子返回值。

5 个答案:

答案 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)

或者,使用refdosync事务块实现相同的目标:

(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))从向量中返回第一个元素,并通过一次操作更新原子。