2016-05-21 7 views
3

Ich frage mich, warum die folgenden 2 Anrufungen unterschiedlich auf verhalten sich je nachdem, ob die ensure Funktion innerhalb eingeführt wird oder außerhalb let:"Sicherstellen" -Funktion in Clojure funktioniert in `Let` falsch?

=> "inside let" 
(def account (ref 1000)) 
(def secured (ref false)) 
(def started (promise)) 
=> #'user/account 
=> #'user/secured 
=> #'user/started 
(defn withdraw [account amount secured] 
    (dosync 
    (let [secured-value (ensure secured)] 
     (deliver started true) 
     (Thread/sleep 5000) 
     (println :started) 
     (when-not secured-value 
     (alter account - amount)) 
     (println :finished)))) 
=> #'user/withdraw 
(future (withdraw account 500 secured)) 
@started 
(dosync (ref-set secured true)) 
=> #<[email protected]: :pending> 
=> true 
:started 
:finished 
=> true 
@account 
=> 500 

========

=> "outside let" 
(def account (ref 1000)) 
(def secured (ref false)) 
(def started (promise)) 
=> #'user/account 
=> #'user/secured 
=> #'user/started 
(defn withdraw [account amount secured] 
    (dosync 
    (let [secured-value @secured] 
     (deliver started true) 
     (Thread/sleep 5000) 
     (println :started) 
     (when-not (ensure secured) 
     (alter account - amount)) 
     (println :finished)))) 
=> #'user/withdraw 
(future (withdraw account 500 secured)) 
@started 
(dosync (ref-set secured true)) 
=> #<[email protected]: :pending> 
=> true 
=> true 
:started 
:started 
:finished 
@account 
=> 1000 

Die erwartete Semantik hier ist, dass, wenn secured auf true gesetzt ist, sollte man nicht in der Lage sein, Geld abzuziehen.

Mein Verständnis ist, dass ensure Funktion wird sicherstellen, dass die secured ref nicht während der Zeitspanne der Transaktion geändert hat, so scheint das zweite Verhalten mit Transaktion Neustart sinnvoll, aber warum verhält es sich anders im ersten Fall?

Update: versuchten ohne Tread/sleep:

(def account (ref 1000)) 
(def secured (ref false)) 
(def started (promise)) 
=> #'user/account 
=> #'user/secured 
=> #'user/started 
(defn withdraw [account amount secured] 
    (dosync 
    (let [secured-value (ensure secured)] 
     (deliver started true) 
     ;(Thread/sleep 5000) 
     (println :started) 
     (when-not secured-value 
     (alter account - amount)) 
     (println :finished)))) 
=> #'user/withdraw 
@account 
=> 1000 
(future (withdraw account 500 secured)) 
@started 
(dosync (ref-set secured true)) 
=> #<[email protected]: :pending> 
:started 
:finished 
=> true 
=> true 
@account 
=> 500 

Mit einem bisschen mehr experimentellen Testen eines ref-set

(def account (ref 1000)) 
(def secured (ref false)) 
(def started (promise)) 
=> #'user/account 
=> #'user/secured 
=> #'user/started 
(defn withdraw [account amount secured] 
    (dosync 
    (let [secured-value (ensure secured)] 
     (deliver started true) 
     (Thread/sleep 5000) 
     (println :started) 
     (when-not secured-value 
     (alter account - amount)) 
     (println :finished)))) 
=> #'user/withdraw 
(future (withdraw account 500 secured)) 
@started 
(dosync do ((println "change started") (ref-set secured true) (println "change done."))) 
=> #<[email protected]: :pending> 
=> true 
change started 
... 
change started 
change started 
:started 
:finished 
change done. 
NullPointerException user/eval2176/fn--2177 (form-init3061788549693294520.clj:3) 
@account 
=> 500 

Antwort

2

Zuerst werde ich Ihre Frage neu zu formulieren (um sicherzustellen, dass wir auf der gleichen Seite sind):

Aufgrund des gleichzeitigen Aufrufs (ref-set secured true) erwarte ich, dass die withdraw-Transaktion in beiden Fällen fehlschlägt (und neu startet), aber ich beobachte nur einen Neustart im Nicht-Let-Fall. Warum???

Dies ist aufgrund einiger Implementierungsdetails von STM in Clojure; speziell die Tatsache, dass Refs mit Lesern/Writern geschützt sind.

In Ihrem ersten Beispiel (unter Verwendung von let) Sie (ensure secured)vor Sie Thread/sleep rufen nennen. Da ensure einen read-lock auf dem Zielrefraktionsblock ergreift, bedeutet dies, dass Ihr ref während der 5-sekündigen Schlafverzögerung schreibgeschützt ist. Da Ihre gleichzeitige (ref-set secured true) eine Schreibsperre auf secured benötigt, um abzuschließen, wird diese Transaktion verzögert, bis die withdraw Transaktion abgeschlossen ist. Aus diesem Grund beobachten Sie in diesem Fall keinen Neustart - die internen Sperren in der STM-Implementierung erzwingen, dass die Schreibtransaktion wartet, bis die Lesetransaktion abgeschlossen ist.

Im Gegensatz dazu sind in Ihrem zweiten Beispiel rufen Sie (ensure secured)nach Sie Thread/sleep nennen. Dies bedeutet, dass der Transaktion nicht bewusst ist, dass sie einen konsistenten Wert für die 5-Sekunden-Schlafverzögerung benötigt. Da die Transaktion nichts unternommen hat, um den Wert von secured zu schützen (d. H. Sie hat es nicht gesperrt), bedeutet dies, dass jede andere Transaktion den Wert secured während dieser 5-sekündigen Verzögerung vor dem ensure Aufruf frei ändern kann. Nach dem Aufruf (ensure secured) wird die Transaktion darauf aufmerksam gemacht, dass sie einen konsistenten Wert für die secured Referenz benötigt.In Ihrem Beispiel hat der gleichzeitige Aufruf ref-set diesen Wert geändert, sodass die Transaktion withdraw neu beginnen muss.

+0

Nachdem ich Ihre zweite Antwort noch einmal gelesen habe, habe ich ein wenig mit dem 'ref-set 'experimentiert (Ergebnisse werden zu der Frage hinzugefügt - nicht sicher *** was NPE verursacht, ***), sieht aus wie' sicherstellen ' blockiert in der Tat den Schreibvorgang, bis der "Zurückziehen" -Tx beendet ist. Also zu meiner Frage früher: "Bedeutet dieser *** Schutz ***, dass er irgendwelche Änderungen ignoriert, die außerhalb des aktuellen Senders an dem Ref vorgenommen wurden, oder bedeutet dies, dass er nach irgendwelchen externen Änderungen des Refs Ausschau hält und den aktuellen Tx neu startet wenn sie auftreten? " Du hast gesagt, dass das "Letztere" richtig ist, also sagst du, dass du erkannt hast, dass das erstgenannte Recht haben würde. Richtig? –

+1

Die NPE ist, weil Sie ein '(' an der falschen Stelle in der aktualisierten Ref-Set-Code. Es sollte vor dem 'tun' sein. – DaoWen

+0

Auch scheint das Verhalten im Gegensatz zu' sichern''s Dokumentation: " Erlaubt mehr Parallelität als (Ref-set ref @ref) "- Ich bin mir nicht sicher, wie all diese Sperren mehr Concurrency ermöglichen. Klingt anders als das, was Rich hier erklärt: [https://groups.google.com/forum/ #! topic/clojure/vxlzONoaba4]. –