0
votes

I'm trying to update a nested counter in an atom (a map) from multiple threads, but getting unpredictable results.

  (def a (atom {:id {:counter 0}}))

  (defn upvote [id]
    (swap! a assoc-in [(keyword id) :counter] (inc (get-in @a [(keyword id) :counter])))
  )

  (dotimes [i 10] (.start (Thread. (fn [] (upvote "id")))))
  (Thread/sleep 12000)
  (prn @a) 

I'm new to Clojure so very possible I'm doing something wrong, but can't figure out what. It's printing a counter value with results varying from 4-10, different each time.

I want to atomically update the counter value and hoped that this approach would always give me a counter value of 10. That it would just retry upon failure and eventually get to 10.

It's for an up-vote function that can get triggered concurrently.

Can you see what I'm doing wrong here?

1
Also consider using an agent instead of an atom, if you don't need the calling thread to know the results of the modification right away. Agents implicitly serialize modifications, so you don't have to worry about being out of sync.amalloy

1 Answers

6
votes

You are updating the atom non-atomically in your code. You first get its value by @a, and then apply it using the swap function. The value may change in between.

The atomic way to update the value is to use a pure function within swap, without referring to the previous atom value via @:

(defn upvote [id]
  (swap! a update-in [(keyword id) :counter] inc))