How to synchronize read and write in Clojure?

In a web application, I am trying to create a unique thread identifier from a limited pool of identifiers. The problem I am facing is that the data structure may already change between reading and writing to another stream; so 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 read and write without having to execute the STM "manually" and without abusing the field in the sid-pool as a return value from swap! ?

+6
source share
3 answers

You can do this with an atom by adding a field to the sid-pool as you seem to be suggesting. I agree that a little rude, but using compare-and-swap! for something so simple is honest. Use an atom instead; or ref, which allows you to return everything you want from the dosync block while maintaining transaction security:

 (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)))))) 
+5
source

I may be confused by what you are trying to do, but the canonical way to create unique identifiers in Clojure would be simple:

 (let [counter (atom 0)] (defn get-unique-id [] (swap! counter inc))) 

No need for any complicated lock. Note:

  • A closure encapsulates an atom bound, so you can be sure that no one else can touch it.
  • Operation swap! provides atomic safety in parallel situations, so the get-unique-id function can be used for different threads.
+2
source
 (def sid-batch 10) (def sid-pool (atom {:cnt 0 :sids '()})) (defn get-sid [] (first (:sids (swap! sid-pool (fn [{:keys [cnt sids]}] (if-let [sids (next sids)] {:cnt cnt :sids sids} {:sids (range cnt (+ sid-batch cnt)) :cnt (+ cnt sid-batch)})))))) 

As I said in my comment, I think you have the right idea: "abuse the field in sid-pool". Besides the fact that you do not need a field, just call (comp first sids) on the return value from swap!

I removed inc in the range call because it caused the generator to skip a multiple of 10.

And return the sid to the pool:

 (defn return-sid [sid] (swap! sid-pool (fn [{:keys [cnt [_ & ids]]}] {:cnt cnt :sids (list* _ sid ids)}))) 
+2
source

Source: https://habr.com/ru/post/912035/


All Articles