Способ синхронизации чтения и записи в Clojure? - PullRequest
6 голосов
/ 30 марта 2012

В веб-приложении я пытаюсь создать уникальный потокобезопасный идентификатор из ограниченного пула идентификаторов.Проблема, с которой я сталкиваюсь, заключается в том, что между чтением и записью другого потока, возможно, уже изменилась структура данных;Вот почему я должен прибегнуть к 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 ]

5 голосов
/ 30 марта 2012

Вы можете сделать это с атомом, добавив поле к 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))))))
2 голосов
/ 30 марта 2012
(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!

Я удалил значение inc в вызове range, потому что генератор пропустил кратные 10.

и вернуть sid в пул:

(defn return-sid [sid]
  (swap! sid-pool (fn [{:keys [cnt [_ & ids]]}]
                    {:cnt cnt
                     :sids (list* _ sid ids)})))
2 голосов
/ 30 марта 2012

Возможно, я запутался в том, что вы пытаетесь сделать, но канонический способ создания уникальных идентификаторов в Clojure был бы просто:

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

Нет необходимости в какой-либо сложной блокировке.Обратите внимание, что:

  • Закрытие заключает в себе атом, привязанный к точке, поэтому вы можете быть уверены, что никто другой не сможет к нему прикоснуться.
  • Операция swap! обеспечивает атомную безопасность в параллельных ситуацияхтаким образом, функция get-unique-id может совместно использоваться различными потоками.
...