6

在一个网络应用程序中,我试图从一个有限的 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!

4

3 回答 3

5

你可以用一个原子来做到这一点,sid-pool按照你似乎建议的方式添加一个字段。我同意这有点粗俗,但是compare-and-swap!用于如此简单的事情是可怕的。相反,使用原子;或者一个 ref,它可以让你从一个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))))))
于 2012-03-30T10:16:41.953 回答
2

也许我对您正在尝试做的事情感到困惑,但在 Clojure 中创建唯一 ID 的规范方法只是:

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

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

  • 闭包封装了 let-bound 原子,因此您可以确定没有其他人可以触摸它。
  • swap!操作确保并发情况下的原子安全,因此该get-unique-id函数可以在不同线程之间共享。
于 2012-03-30T10:09:00.650 回答
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 中的字段”有正确的想法。除非您不需要字段,只需在交换的返回值上调用 (comp first sids) 即可!

我删除了对 range 的调用中的 inc,因为它导致生成器跳过了 10 的倍数。

并将 sid 返回到池中:

(defn return-sid [sid]
  (swap! sid-pool (fn [{:keys [cnt [_ & ids]]}]
                    {:cnt cnt
                     :sids (list* _ sid ids)})))
于 2012-03-30T10:36:09.570 回答