【问题标题】:Remove first item from Clojure vector atom and return it从 Clojure 向量原子中删除第一项并返回它
【发布时间】:2014-04-20 00:26:02
【问题描述】:

我有一个原子包裹了一个元素的向量:

(def items (atom [1 2 3 4]))

我想自动删除第一项并返回它。这段代码说明了逻辑:

(let [x (first @items)]
  (swap! items #(subvec % 1))
  x)

但是当多个线程相互竞争时,上面的代码是不正确的。读取和更新之间存在竞争条件。

正如this answer 中所说的,原子用于不协调的同步访问。我希望这可以用原子而不是 ref 来完成,因为原子更简单。

有没有只使用原子而不使用引用的解决方案? (我将尝试使用手表,看看情况如何。)如果你的答案坚持需要一个参考,你能否解释一下为什么需要参考,即使当一个人想要“协调同步访问许多身份时建议使用参考” "(与上面相同的链接)。

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

【问题讨论】:

  • 作为记录,Clojure Google 小组How to update an atom & return the change? 中可能有类似的讨论。
  • 好问题!!!!!!
  • 您添加了标签atom ...但您的问题与它有什么关系? (网络提要格式)

标签: clojure


【解决方案1】:

带有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)))

【讨论】:

  • 我认为这样做所需的长度表明这超出了原子的预期用途。 :) 但是,不错的 hack。
  • @AlexMiller 我也这么认为——直到我查看了the swap! source in Atom.java,它也依赖于.compareAndSet 周围的循环。我现在认为这是对原子的一种很好的利用。为什么要为这个简单的用例麻烦一个 ref?
  • 看起来 Prismatic 已经有一段时间了,在 plumbing 中命名为 swap-pair! 更好。
  • Prismatic 管道swap-pair 的链接目前为github.com/Prismatic/plumbing/blob/master/src/plumbing/…
【解决方案2】:

如果您需要使用原子,请使用本地封装的原子来存储获胜交易的交易内价值的第一个元素。

(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 产生影响,直到它可以重试,以便执行读写操作而不会被另一个写入操作中断。

【讨论】:

    【解决方案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...
    

    是的,您可能想处理这种情况。 ;)

    【讨论】:

    • +1 表示这种在没有其他原子的情况下存储事务中值的方法。也许命名空间的 kw 会使解决方案更通用。
    【解决方案4】:

    这不是此用例的解决方案,但可能适用于其他一些用例。

    您可以使用add-watch 在原子上创建一个监视,它将发送一个包含旧值和新值的事件。

    【讨论】:

      【解决方案5】:

      由于在 Clojure 1.9 中添加了 swap-vals!,您可以使用 (ffirst (swap-vals! items rest)) 从向量中返回第一个元素并在单个操作中更新原子。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-08-14
        • 1970-01-01
        • 2012-03-06
        • 1970-01-01
        • 1970-01-01
        • 2021-04-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多