Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Way to synchronize reads and writes in Clojure?

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!?

like image 553
Philip Kamenarsky Avatar asked Mar 30 '12 09:03

Philip Kamenarsky


1 Answers

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))))))
like image 184
amalloy Avatar answered Sep 20 '22 04:09

amalloy