6

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

sid-pool「手動で」STM を実行する必要がなく、フィールドをからの戻り値として悪用することなく、読み取りと書き込みを同期する簡単な方法はありswap!ますか?

4

3 に答える 3

5

sid-pool提案しているように見える方法でフィールドを追加することにより、アトムを使用してそれを行うことができます。私はそれが少し粗雑であることに同意します、しかしcompare-and-swap!とても単純な何かのために使うことはひどいです。代わりに、アトムを使用してください。dosyncまたはref。これにより、トランザクションの安全性を維持しながら、ブロックから必要なものを返すことができます。

(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 結合アトムをカプセル化するため、他の誰もそれに触れることができないことを確認できます。
  • この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 のフィールドを悪用する」という考えは正しいと思います。フィールドが必要ない場合を除いて、swap からの戻り値で (comp first sids) を呼び出すだけです!

ジェネレーターが 10 の倍数をスキップする原因となったため、range の呼び出しで inc を削除しました。

sid をプールに返すには:

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