在Clojure中同步读写的方法?

时间:2012-03-30 09:33:56

标签: transactions clojure synchronization state stm

在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!的返回值?

3 个答案:

答案 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)))

不需要任何复杂的锁定。请注意:

  • 封闭封装了let-bound atom,因此您可以确定没有其他人可以触摸它。
  • 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)})))