9

「Clojure プログラミング」(Emerick、O'Reilly) は次のように述べています。

(...) 現在のトランザクションの開始以降に別のトランザクションによって新しい値がコミットされた場合、トランザクションの開始時点での ref の新しい値は提供できません。幸いなことに、STM はこの問題に気付き、トランザクションに関係する参照の状態の限定された履歴を維持します。履歴のサイズは再試行ごとに増加します。これにより、ある時点でトランザクションを再試行する必要がなくなる可能性が高くなります。これは、ref が同時に更新されている間、目的の値がまだ履歴に存在するためです。

次に、問題を説明するためにいくつかのコード サンプルを示します。

最初に、すべての書き込みトランザクションが完了した後にのみ読み取りトランザクションが成功することを説明します (したがってa = 500):

(def a (ref 0))
(future (dotimes [_ 500] (dosync (Thread/sleep 20) (alter a inc))))
@(future (dosync (Thread/sleep 1000) @a))
; 500
(ref-history-count a)
; 10

次に、その設定を説明し:min-history:max-historyリーダー トランザクションの再試行に役立つ場合があります (今回aは以前に正常に読み取られました - 値は 33 です)。

(def a (ref 0 :min-history 50 :max-history :100))
(future (dotimes [_ 500] (dosync (Thread/sleep 20) (alter a inc))))
@(future (dosync (Thread/sleep 1000) @a))
; 33

derefリーダートランザクション内で再試行が発生する理由を理解しています(一部のライタートランザクションが参照への変更をコミットしている場合)。私が理解していないのはこの部分です:「これにより、ある時点でトランザクションを再試行する必要がなくなる可能性が高くなります。これは、参照が同時に更新されている間、目的の値がまだ履歴に存在するためです」.

「望ましい値」とは何ですか?上記の例では、時間の経過とともに参照履歴がどのように変化しますか? 参照履歴がどのように機能するかを示すタイムラインの説明または例を教えてもらえますか?

4

1 に答える 1

13

Clojure の STM は現在を気にしません。観察が行われる頃には、現在はすでに動いています。Clojure の STM は、状態の一貫したスナップショットを取得することのみを考慮します。

単一の読み取りが常に一貫したスナップショットになることがわかっているため、これはこの例からはあまり明白ではありません。ただし、dosync単一の のみを使用している場合は、おそらくs をref使用するべきではなく、代わりに s を使用する必要があります。refatom

したがって、代わりに anaと aから読み取り、bそれらの合計を返そうとしていると想像してください。私たちはそれを気にせずab合計を返すときに最新です.現在に追いつくことは無駄です。私たちが話しているのはそれaでありb、一貫した期間からのものです。

dosyncブロック内で読み取りをa行っbた後b、2 つの読み取りの間に更新された場合、一貫性のない時点からのaandが発生します。bもう一度やり直さなければなりません。最初からやり直して、近い現在から読んでみてaくださいb

b場合を除き...への変更ごとにの履歴を保持しているとしbます。前と同じように、読んだa後、完了する前にbへの更新がb発生したとします。の履歴を保存したので、変更b前にさかのぼってb一貫性のあるaとを見つけることができますb。次に、一貫性のあるab近い過去から、一貫性のある合計を返すことができます。近い現在からの新しい値で再試行する必要はありません (そして、再度失敗する可能性もあります)。


入力時のスナップショットと出力時のスナップショットを比較することで整合性が保たれdosyncます。このモデルでは、その間に関連データを変更すると、再試行が必要になります。デフォルトでは、これが当てはまると楽観的です。障害が発生した場合、該当するものにマークが付けrefられるため、次回の変更時に履歴が保持されます。これで、開始時に取得したスナップショットを終了時のスナップショットと比較できるか、単一の過去の履歴を保持できる場合は常に一貫性が維持されます。そのため、これを 1 回変更しても、失敗することはありませんrefdosync履歴がなくなるため、2 つの変更が残ります。別の障害が発生した場合、これは再びマークされ、長さ 2 の履歴が維持されます。

この例では、複数の参照を調整しようとしているふりをします。デフォルトの初期履歴の長さは 0 で、最大 10 です。

(defn stm-experiment 
  [min-hist max-hist] 
  (let [a (ref 0 :min-history min-hist :max-history max-hist)] 
    (future (dotimes [_ 500] (dosync (Thread/sleep 20) (alter a inc)))) 
    (dosync (Thread/sleep 1000) @a)))

したがって、デフォルトは

(stm-experiment 0 10)
;=> 500 (probably)

更新はa20 ミリ秒ごとに発生し、読み取りは 1000 ミリ秒後に発生します。aしたがって、各読み取り試行の前に 50 回の更新が発生します。min-history と max-history のデフォルトの調整では、楽観的に 0 回の更新が発生しa、最大で 10 回の更新が発生します。つまり、履歴がない状態で開始しa、障害が発生するたびに履歴をa1 つ増やしますが、最大 10 までです。50 回の更新が発生しているため、これでは十分ではありません。

比較する

(stm-experiment 50 100)
;=> 0 (quite possibly, multicore)

履歴が 50 の場合、 への 50 の変更すべてがa履歴に保持されるため、エントリ時にキャプチャした の状態は、a終了時に履歴キューの最後に残っています。

こちらもお試しください

(stm-experiment 48 100)
;=> 100 (or thereabouts, multicore)

最初の履歴の長さが 48 の場合、50 に変更するaと、履歴が使い果たされ、読み取りエラーが発生します。しかし、この読み取り障害は履歴を 49 に延長します。これでもまだ十分ではないため、別の読み取り障害が発生し、履歴が 50 に延長されます。これで、履歴と成功の先頭にa一致することがわかります。が更新された 2 回の試行後に発生します。adosynca50 x 2 = 100

ついに、

(stm-experiment 48 48)
;=> 500

a履歴の長さの上限が 48 であるため、 50 回の更新が発生する前に開始した値を見つけることはできません。

于 2014-02-24T02:38:35.047 に答える