Clojure

Refsとトランザクション

目次

Varsはスレッド分離によって可変ストレージロケーションの安全な使用を保証しますが、トランザクション参照(Refs)はソフトウェアトランザクショナルメモリ(STM)システムを介して、可変ストレージロケーションの安全な共有使用を保証します。Refsはそのライフサイクル中、単一のストレージロケーションにバインドされ、そのロケーションの変更はトランザクション内でのみ発生することを許可します。

データベーストランザクションを使用したことがある場合、Clojureトランザクションは理解しやすいはずです。これらは、Refsに対するすべてのアクションがアトミックで、一貫性があり、分離されていることを保証します。アトミックとは、トランザクション内で行われたRefsへのすべての変更が行われるか、何も行われないことを意味します。一貫性があるとは、各新しい値がトランザクションのコミットを許可する前に、バリデーター関数でチェックできることを意味します。分離されているとは、トランザクションが実行中に他のトランザクションの影響を受けないことを意味します。STMに共通するもう1つの機能は、トランザクションの実行中に競合が発生した場合、自動的に再試行されることです。

STMを実行する方法は多数(ロッキング/悲観的、ロックフリー/楽観的、ハイブリッド)あり、依然として研究課題です。Clojure STMは、マルチバージョン同時実行制御スナップショット分離のために適応型履歴キューと組み合わせて使用し、明確なcommute操作を提供します。

実際には、これは次のことを意味します。

  1. Refsのすべての読み取りは、トランザクションの開始時点(その「読み取りポイント」)における「Refの世界」の一貫したスナップショットを確認します。トランザクションは、トランザクションが行った変更を確認します。これはトランザクション内値と呼ばれます。

  2. トランザクション中にRefsに対して行われたすべての変更(ref-setalter、またはcommute経由)は、「Refの世界」タイムラインの単一のポイント(その「書き込みポイント」)で発生するように見えます。

  3. このトランザクションによってref-set / alter / ensureされたRefsには、他のトランザクションによる変更は行われていません。

  4. このトランザクションによってcommuteされたRefsには、他のトランザクションによって変更が行われている可能性があります。commuteによって適用される関数は可換であるため、問題ないはずです。

  5. リーダーとコミュートは、ライター、コミュート、または他のリーダーをブロックすることはありません。

  6. ライターは、コミュートやリーダーをブロックすることはありません。

  7. トランザクションは必ず再試行されるため、トランザクションではI/Oやその他の副作用のあるアクティビティは避ける必要があります。 io! マクロを使用すると、トランザクション内で純粋でない関数の使用を防ぐことができます。

  8. 変更されるRefの値の有効性に関する制約が、変更されていないRefの同時値に依存している場合、2番目のRefはensureを呼び出すことによって変更から保護できます。 このように「ensure」されたRefsは保護されます(項目3)が、世界は変更しません(項目2)。

  9. Clojure MVCC STMは、永続的なコレクションを使用するように設計されており、Refsの値としてClojureコレクションを使用することを強くお勧めします。STMトランザクションで行われるすべての作業は投機的であるため、コピーと変更を行うコストが低いことが不可欠です。永続的なコレクションには、無料のコピーがあり(変更できないため、元のコピーを使用するだけです)、「変更」は構造を効率的に共有します。いずれにしても

  10. Refsに配置される値は、不変であるか、不変と見なす必要があります !! そうでない場合、Clojureはあなたを助けることができません。

この例では、ベクターへの参照のベクターが作成され、それぞれに(最初は連続した)一意の番号が含まれています。次に、スレッドのセットが開始され、トランザクション内で、2つのランダムなベクター内の2つのランダムな位置を繰り返し選択して交換します。トランザクションの使用以外に、必然的な競合を防ぐための特別な努力は行われていません。

(defn run [nvecs nitems nthreads niters]
  (let [vec-refs (vec (map (comp ref vec)
                           (partition nitems (range (* nvecs nitems)))))
        swap #(let [v1 (rand-int nvecs)
                    v2 (rand-int nvecs)
                    i1 (rand-int nitems)
                    i2 (rand-int nitems)]
                (dosync
                 (let [temp (nth @(vec-refs v1) i1)]
                   (alter (vec-refs v1) assoc i1 (nth @(vec-refs v2) i2))
                   (alter (vec-refs v2) assoc i2 temp))))
        report #(do
                 (prn (map deref vec-refs))
                 (println "Distinct:"
                          (count (distinct (apply concat (map deref vec-refs))))))]
    (report)
    (dorun (apply pcalls (repeat nthreads #(dotimes [_ niters] (swap)))))
    (report)))

実行すると、シャッフルで値が失われたり重複したりすることはありません。

(run 100 10 10 100000)

([0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19] ...
 [990 991 992 993 994 995 996 997 998 999])
Distinct: 1000

([382 318 466 963 619 22 21 273 45 596] [808 639 804 471 394 904 952 75 289 778] ...
 [484 216 622 139 651 592 379 228 242 355])
Distinct: 1000

Refを作成する:ref

Refを調べる:deref ( @ reader マクロも参照)

トランザクションマクロ:dosync io!

トランザクション内でのみ許可される:ensure ref-set alter commute

Refバリデーター:set-validator! get-validator