In a web app I'm trying to generate a unique thread safe id from a limited id pool. The problem I'm facing is that between reading and writing another thread may already have changed the data structure; this is why I have to resort to compare-and-set!
.
(def sid-batch 10)
(def sid-pool (atom {:cnt 0
:sids '()}))
(defn get-sid []
(let [{:keys [cnt sids] :as old} @sid-pool]
; use compare-and-set! here for atomic read & write
(if (empty? sids)
; generate more sids
(if (compare-and-set!
sid-pool
old
(-> old
(assoc :sids (range (inc cnt) (+ sid-batch cnt)))
(assoc :cnt (+ cnt sid-batch))))
; return newest sid or recur till "transaction" succeeds
cnt
(recur))
; get first sid
(if (compare-and-set! sid-pool old (update-in old [:sids] next))
; return first free sid or recur till "transaction" succeeds
(first sids)
(recur)))))
Is there an easier way to synchronize reads and writes without having to perform STM "by hand" and without abusing a field in sid-pool
as a return value from swap!
?
You can do it with an atom, by adding a field to sid-pool
in the way you seem to be suggesting. I agree that's a little gross, but using compare-and-swap!
for something so simple is godawful. Instead, use the atom; or a ref, which lets you return whatever you want from a dosync
block while still being transactionally safe:
(defn get-sid []
(dosync
(let [{:keys [cnt sids]} @sid-pool]
(if (empty? sids)
(do
(alter sid-pool
(fn [old]
(-> pool
(assoc :sids (range (inc cnt) (+ sid-batch cnt)))
(update-in [:cnt] + sid-batch))))
cnt)
(do
(alter sid-pool update-in [:sids] next)
(first sids))))))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With