在Clojure中同步读取和写入的方式?
在一个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!
的返回值swap!
?
你可以用一个原子来做到这一点,通过你似乎暗示的方式向sid-pool
添加一个字段。 我同意这有点粗俗,但使用compare-and-swap!
对于这么简单的事情来说,这是一个苦苦挣扎 相反,使用原子; 或者一个ref,它可以让你从dosync
块中返回任何你想要的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))))))
(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中的字段”。 除了你不需要一个字段,只需调用(comp第一个sids)来自swap的返回值!
我将呼叫中的公司移除范围,因为它导致生成器跳过10的倍数。
为了回到游泳池:
(defn return-sid [sid]
(swap! sid-pool (fn [{:keys [cnt [_ & ids]]}]
{:cnt cnt
:sids (list* _ sid ids)})))
也许我对你想要做的事情感到困惑,但是在Clojure中创建唯一ID的规范方法可能仅仅是:
(let [counter (atom 0)]
(defn get-unique-id []
(swap! counter inc)))
不需要任何复杂的锁定。 注意:
swap!
操作可确保并发情况下的原子安全性,因此可以在不同线程之间共享get-unique-id
函数。 上一篇: Way to synchronize reads and writes in Clojure?
下一篇: Removing the contents of a Chan or MVar in a single discrete step