2014-02-23 4 views
9

„Clojure-Programmierung“ (Emerick, O'Reilly) heißt es, dass:Deref innerhalb einer Transaktion kann einen Wiederholungsversuch auslösen - welche Rolle spielt der ref-Statusverlauf?

(...), wenn ein neuer Wert, wenn seit dem Beginn von einer anderen Transaktion verpflichtet der aktuellen Transaktion, um den neuen Wert des ref ab dem Beginn der Transaktion kann nicht zur Verfügung gestellt werden. Hilfreicherweise bemerkt das STM dieses Problem und behält eine begrenzte Historie der Zustände von refs bei, die an einer Transaktion beteiligt sind, wobei die Größe des Verlaufs bei jedem erneuten Versuch erhöht wird. Dies erhöht die Wahrscheinlichkeit, dass die Transaktion an einem bestimmten Punkt nicht mehr wiederholt werden muss, da, während der Ref korrekt aktualisiert wird, der gewünschte Wert immer noch in der Historie vorhanden ist.

Als nächstes geben sie einige Code-Beispiele zur Veranschaulichung des Problems.

Erstens, dass das Lesen Transaktion zu erläutern wird nur gelingen, wenn alle Schriftsteller Transaktionen durchgeführt werden (also 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 

Und zweitens, um darzustellen, dass :min-history und :max-history Einstellung mit Lesern Transaktion Wiederholungen helfen kann (dies Zeit a hat früher erfolgreich gelesen wurde - Wert von 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 

ich, warum deref innerhalb der Leser Transaktion verstehen bewirkt, dass es erneut versucht wird (wenn einige Writer-Transaktionen Änderungen an ref vornehmen). Was ich nicht verstehe, ist dieser Teil: "Dies erhöht die Chance, dass die Transaktion irgendwann nicht mehr wiederholt werden muss, da der Ref-Wert zwar konstant aktualisiert wird, der gewünschte Wert aber immer noch in der Historie vorhanden ist".

Was ist der "gewünschte Wert"? Wie ändert sich die Referenzgeschichte im Laufe der Zeit in den obigen Beispielen? Kann mich jemand auf eine Erklärung oder ein Beispiel mit einer Zeitleiste verweisen, die zeigt, wie die ref-Geschichte funktioniert?

Antwort

13

Clojure STM kümmert sich nicht um die Gegenwart. Wenn eine Beobachtung gemacht wird, hat sich die Gegenwart bereits bewegt. Clojures STM kümmert sich nur darum, einen konsistenten Zustandsmoment zu erfassen.

Dies ist nicht sehr offensichtlich aus dem Beispiel, weil wir wissen, dass ein einzelner Lesevorgang immer ein konsistenter Snapshot wäre. Aber, wenn Sie immer nur dosync auf einem einzigen ref verwenden, dann sollten Sie wahrscheinlich nicht ref s, sondern atom s stattdessen verwenden.

Also, stellen Sie sich vor, wir lesen von einem a und einem b und versuchen, ihre Summe zurückzugeben. Es ist uns egal, dass a und b aktuell sind, wenn wir die Summe zurückgeben - der Versuch, mit der Gegenwart Schritt zu halten, ist zwecklos. Alles worum es geht ist, dass a und b aus einem konsistenten Zeitraum stammen.

Wenn während in einem dosync Block lesen wir a und dann b aber b in aktualisiert wurde zwischen den beiden liest, haben wir eine a und b inkonsistente Zeitpunkten. Wir müssen es noch einmal versuchen - beginnen Sie noch einmal und versuchen Sie a dann b aus der Nähe zu lesen.

Es sei denn ... Angenommen, hielten wir eine Geschichte von b für jede Änderung b.Wie zuvor angenommen, wir lesen a und dann b, aber ein Update auf b tritt auf, bevor wir fertig sind. Da wir eine Historie von b gespeichert haben, können wir in der Zeit zurückgehen, bevor b geändert wird und eine konsistente a und b finden. Dann können wir mit einer konsistenten a und b aus der nahen Vergangenheit eine konsistente Summe zurückgeben. Wir müssen nicht erneut versuchen (und möglicherweise erneut fehlschlagen) mit neuen Werten aus der nahen Gegenwart.


Konsistenz wird durch Vergleichen einer Momentaufnahme gehalten wird, wenn auf eine dosync snaphshot Eingabe beim Verlassen. Bei diesem Modell würde jede Änderung der relevanten Daten dazwischen eine Wiederholung erfordern. Der Standardwert ist optimistisch, dass dies der Fall sein wird. Wenn ein Fehler auftritt, wird er auf dem entsprechenden ref markiert, so dass bei der nächsten Änderung eine Historie gespeichert wird. Die Konsistenz wird nun beibehalten, wenn der bei der Eingabe erstellte Snapshot beim Beenden mit einem Snapshot verglichen oder der einzelne vergangene Verlauf beibehalten wird. Also, jetzt eine einzige Änderung an dieser ref während der dosync wird keinen Fehler verursachen. Zwei Änderungen werden noch gemacht, weil die Geschichte erschöpft sein wird. Tritt erneut ein Fehler auf, wird dieser erneut markiert und es wird nun eine Historie der Länge 2 gepflegt.

Mit dem Beispiel tun, als ob wir versuchen, mehrere Referenzen zu koordinieren. Die anfängliche Standard Geschichte Länge ist 0 mit einem Maximum von 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))) 

So ist die Standard

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

Die Updates zu a treten alle 20 Millisekunden betragen würde und die Lese tritt nach 1000 Millisekunden. Daher werden vor jedem Leseversuch 50 Aktualisierungen auf a durchgeführt. Die Standardeinstellungen von min-history und max-history lauten, dass a optimistisch 0 Aktualisierungen passieren werden und dass höchstens 10 werden. Das heißt, wir beginnen mit keiner Geschichte auf a und jedes Mal, wenn ein Fehler auftritt, wachsen wir die Geschichte von a eine länger, aber nur bis zu 10. Da 50 Updates auftreten, wird dies nie genug sein.

vergleichen

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

Mit einer Geschichte von 50, alle 50 Änderungen an a in einer Geschichte gehalten, damit der Zustand des a, die wir bei der Einreise erfaßt noch ganz am Ende der Geschichte gibt es Warteschlange beim Beenden.

versuchen, mit einer anfänglichen Geschichte Länge von 48, die 50 Änderungen an a bewirkt, dass die Geschichte zu erschöpfen und ein Lesefehler

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

auch. Aber dieser Lesefehler verlängert den Verlauf auf 49. Dies ist immer noch nicht genug, so dass ein weiterer Lesefehler auftritt und der Verlauf auf 50 verlängert wird. Nun kann ein a übereinstimmend mit dem a Anfang der dosync gefunden werden Geschichte und Erfolg tritt nach zwei Versuchen auf, bei denen a50 x 2 = 100 mal aktualisiert wurde.

Schließlich

(stm-experiment 48 48) 
;=> 500 

mit einer Obergrenze von 48 auf der Geschichte Länge, können wir nie den Wert von a finden wir begonnen, bevor 50 Updates aufgetreten.

+0

Sie haben es ziemlich genagelt :) Perfekte Antwort, danke! – kamituel