pontus222
pontus222

Reputation: 79

swap! value in atom (nested-map) Clojure

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?

Upvotes: 0

Views: 815

Answers (1)

Aleph Aleph
Aleph Aleph

Reputation: 5395

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

Upvotes: 6

Related Questions