11

I have an atom wrapping a vector of items:

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

I want to atomically remove the first item and return it. This code illustrates the logic:

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

But the above code is not correct when many threads are contending with each other. There is a race condition between reading and updating.

As stated nicely in this answer, atoms are for uncoordinated synchronous access. I was hoping this could be done with an atom instead of a ref, because the atom is simpler.

Is there a solution that uses only atoms, not refs? (I'm going to try using watches and see how that goes.) If your answer insists that a ref is needed, could you please explain why a ref is needed even though refs are suggested when one wants "Coordinated Synchronous access to Many Identities" (same link as above).

This is different from other related questions such as How do I update a vector element of an atom in Clojure? and Best way to remove item in a list for an atom in Clojure because I want to update a vector atom and return the value.

Community
  • 1
  • 1
David J.
  • 31,569
  • 22
  • 122
  • 174
  • For the record, there was a similar discussion in the Clojure Google group, [How to update an atom & return the change?](https://groups.google.com/forum/?hl=en#!topic/clojure/2dHvX7bf7nA) that might be of interest. – Jakub Holý Mar 22 '14 at 10:11
  • Brilliant question!!!!!! – yazz.com Sep 06 '14 at 18:43
  • You added the tag [tag:atom] … but has your question anything to do with it? (Web feed format) – unor Aug 14 '16 at 22:28

5 Answers5

11

A spin loop with compareAndSet is used to swap! an atom. Clojure also provides a lower level compare-and-set! for atoms that you can use to do your own spin loop and return both the old and new value.

(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)))
A. Webb
  • 26,227
  • 1
  • 63
  • 95
  • 1
    I think the lengths necessary to do this are an indication that this is outside the expected use for atoms. :) But, nice hack. – Alex Miller Mar 14 '14 at 17:27
  • 2
    @AlexMiller I thought so as well -- until I looked at [the `swap!` source in Atom.java](https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Atom.java#L33) which also relies on a loop around `.compareAndSet`. I see this now as a good use of atoms. Why bother with a ref for this simple use case? – David J. Mar 14 '14 at 17:36
  • 1
    Looks like Prismatic has had this for a while, with the better named `swap-pair!` in [plumbing](https://github.com/Prismatic/plumbing/blob/master/src/plumbing/core.clj#L325). – A. Webb Apr 27 '14 at 19:34
  • The link to Prismatic plumbing's `swap-pair` is currently https://github.com/Prismatic/plumbing/blob/master/src/plumbing/core.cljx#L356 – David J. Sep 17 '15 at 01:20
4

If you need to use an atom, use a locally encapsulated atom to store the first element of the in-transaction value of the winning transaction.

(let [f-atom (atom nil)]
  (swap! items-atom #(do (reset! f-atom (first %)) 
                         (rest %)))
  @f-atom)

Alternatively, achieve the same with a ref and a dosync transaction block:

(dosync
  (let [f (first @items-ref)]
    (alter items-ref rest)
    f)))

Here, in case the transaction fails because a parallel write operation succeeded, the transaction does not return or have effect on the ref until it could have been retried so that the read and write operations were performed without interruption by another write operation.

Leon Grapenthin
  • 9,246
  • 24
  • 37
3

My go-to solution for this is storing whatever return value is needed in the metadata of the contained value. I don't know how idiomatic that is and it obviously only works for classes that implement the IMeta interface.

(defn pop-head!
  [a]
  (-> (swap! a #(with-meta (subvec % 1) {:head (% 0)}))
      (meta)
      :head))

This profits from the fact that swap! returns the now-stored value of the atom. Let's try it:

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

Yeah, you might want to handle that case. ;)

xsc
  • 5,983
  • 23
  • 30
  • +1 for this approach of storing the in-transaction value without another atom. Maybe a namespaced kw would make the solution more general. – Leon Grapenthin Mar 21 '14 at 11:43
1

This is not a solution for this use case, but might be for some others.

You can create a watch on the atom with add-watch which will send an event with both the old and new values.

Alex Miller
  • 69,183
  • 25
  • 122
  • 167
0

Since the addition of swap-vals! in Clojure 1.9, you can use (ffirst (swap-vals! items rest)) to return the first element from the vector and update the atom in a single operation.

Martin Harrigan
  • 1,044
  • 11
  • 28