在Web应用程序中,我正在尝试从有限的ID池中生成唯一的线程安全ID。我面临的问题是,在读写之间,另一个线程可能已经改变了数据结构;这就是为什么我不得不诉诸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)))))
是否有更简单的方法来同步读取和写入,而无需“手动”执行STM并且不会滥用sid-pool
中的字段作为swap!
的返回值?
答案 0 :(得分:5)
您可以使用原子进行操作,方法是以您似乎建议的方式向sid-pool
添加字段。我同意这是一个小粗略,但使用compare-and-swap!
这么简单的东西是神圣的。相反,使用原子;或者一个引用,它允许你从dosync
块返回你想要的任何东西,同时仍然是交易安全的:
(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))))))
答案 1 :(得分:2)
也许我对你要做的事感到困惑,但在Clojure中创建唯一ID的规范方法就是:
(let [counter (atom 0)]
(defn get-unique-id []
(swap! counter inc)))
不需要任何复杂的锁定。请注意:
swap!
操作可确保并发情况下的原子安全性,因此get-unique-id
函数可以在不同的线程之间共享。答案 2 :(得分:2)
(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)}))))))
就像我在评论中所说的那样,我认为你有一个正确的想法是“滥用sid-pool中的字段”。除非你不需要字段,只需在swap的返回值上调用(comp first sids)!
我在调用范围中删除了inc,因为它导致生成器跳过10的倍数。
并将sid返回池中:
(defn return-sid [sid]
(swap! sid-pool (fn [{:keys [cnt [_ & ids]]}]
{:cnt cnt
:sids (list* _ sid ids)})))