I am new to clojure. Now I hava a problem about read
transcation.
Suppose now I write some code about bank transfer. So this is
the source-account and destination-account
//The code
(def source-account (ref 1000))
(def dest-account (ref 0))
And I have a function to do the account transfer.
//The code
(defn take-money [account num]
(- account num))
(defn despoite [account num]
(+ account num))
(defn transfer-money [num]
(dosync (alter source-account take-money num)
(alter dest-account despoite num)))
And I have a another function to print the source account and dest
acoount information.
//The code
(defn print-info []
(do
(println @source-account)
(println @dest-account)))
Suppose the transfer-money and print-info are run in different
thread. With the current Clojure STM, is it possible that print-info
prints the old source-account information and modified dest-account
information? Do I need to add a dosync to the print-info function? I
read the Programming Clojure. It says do not have side-effect on a
transaction. If I add dosync to the print-info, do I need to use agent
to print the info? Hope somebody can help me.
Thank.
Jack
Yes.
> Do I need to add a dosync to the print-info function? I
> read the Programming Clojure. It says do not have side-effect on a
> transaction.
If you want consistent results between refs, you'll need to be in a
transaction, thus dosync. You might also need to use ensure, but I'm
not certain. The reason side-effects are discouraged (not forbidden)
is because a transaction may be run multiple times, thus you might get
multiple printouts.
> If I add dosync to the print-info, do I need to use agent
Agents are desgned to be used for side-effects in a transaction.
That's why sends to an agent are held and only actually happen if the
tx commits. It mIght be overkill for your examle, but yes send
ensured/altered values to an agent from inside a dosync block.
> to print the info?
But surely in this case you could do:
(defn print-info []
(let [[src dst] (dosync [@source-account @dest-account])]
(println src)
(println dst)))
or maybe:
(defn print-info []
(let [[src dst] (dosync
(ensure source-account)
(ensure dest-account)
[@source-account @dest-account])]
(println src)
(println dst)))
I suspect the ensures are necessary, but I would appreciate it if
someone could explain why they are (or are not) necessary in this
case.
Thanks.
--
Michael Wood <esio...@gmail.com>
As far as I know, dosync guarantees that the derefs are consistent
with each other. That is, no other transaction can affect the return
value of the deref while the transaction is running. However, deref
doesn't guarantee that the values read are still current when the
transaction finishes. Some other transaction might have been committed
first. If you absolutely need the most recent values, not just
consistent ones, you need to use ensure.
This can be simplified to
(defn print-info []
(let [[src dst] (dosync
[(ensure source-account)
(ensure dest-account)])]
(println src)
(println dst)))
ensure returns the in-trasaction value of the ref.
There's no guarantee that the refs won't be changed a split second
after the transaction, of course...
To demonstrate the difference between a simple @r and (ensure r) in a
transaction, define the following ref & function at the REPL:
(def r (ref 1))
(defn funk [r]
(dosync
(let [v (ensure r)]
(println v)
(Thread/sleep 5000)
(println v))))
Now launch it in a separate thread:
(.start (Thread. #(funk r)))
It'll print a "1" straight away, but you'll have a moment to type in
another expression before it proceeds with the second println. So, do
this:
(dosync (ref-set r 5))
Notice that this blocks until the second println happens, then resets
the value of the ref afterwards.
With (ensure r) replaced by @r in the definition of funk, the (dosync
(ref-set r 5)) would succeed and the funky transaction would retry,
thus performing three printlns in total.
Thus using ensure may actually make in-transaction side-effects safe
at the cost of some lost concurrency.
Sincerely,
Michał
Ah, thanks.
> There's no guarantee that the refs won't be changed a split second
> after the transaction, of course...
Of course.
> To demonstrate the difference between a simple @r and (ensure r) in a
> transaction, define the following ref & function at the REPL:
>
> (def r (ref 1))
>
> (defn funk [r]
> (dosync
> (let [v (ensure r)]
> (println v)
> (Thread/sleep 5000)
> (println v))))
>
> Now launch it in a separate thread:
>
> (.start (Thread. #(funk r)))
>
> It'll print a "1" straight away, but you'll have a moment to type in
> another expression before it proceeds with the second println. So, do
> this:
>
> (dosync (ref-set r 5))
>
> Notice that this blocks until the second println happens, then resets
> the value of the ref afterwards.
>
> With (ensure r) replaced by @r in the definition of funk, the (dosync
> (ref-set r 5)) would succeed and the funky transaction would retry,
> thus performing three printlns in total.
That does demonstrate the difference (or a difference), but I don't
get 3 printlns.
I modified the printlns to distinguish between the two:
(defn funk [r]
(dosync
(let [v @r]
(println "Start:" v)
(Thread/sleep 5000)
(println "End:" v))))
I assume you were expecting something like:
user=> (.start (Thread. #(funk r)))
Start: 1
nil
user=> (dosync (ref-set r 5))
5
user=> Start: 5
End: 5
?
What I see is:
user=> (.start (Thread. #(funk r)))
Start: 1
nil
user=> (dosync (ref-set r 5))
5
user=> End: 1
This is with Clojure 1.2.0-master-SNAPSHOT
(2855e34106b2cacd4614f2b7e31f1536b4b849bc)
> Thus using ensure may actually make in-transaction side-effects safe
> at the cost of some lost concurrency.
--
Michael Wood <esio...@gmail.com>
Ouch, I actually posted the wrong version of the function. Here's an
improvement in the context of a REPL interaction:
1. ensure version:
user> (defn funk [r]
(dosync
(println (ensure r))
(Thread/sleep 5000)
(println (ensure r))))
#'user/funk
user> (def r (ref 1))
#'user/r
user> (.start (Thread. #(funk r)))
1
nil
user> (dosync (ref-set r 5))
1
5
2. deref version:
user> (defn funk [r]
(dosync
(println @r)
(Thread/sleep 5000)
(println @r)))
#'user/funk
user> (def r (ref 1))
#'user/r
user> (.start (Thread. #(funk r)))
1
nil
user> (dosync (ref-set r 5))
5
5
5
Note how the deref version prints more lines (because it retries the
transaction).
I'm using a newer commit (49a7d6b8e14050a45df5332e768ba6647752215d),
but this shouldn't make any difference.
Sincerely,
Michał
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
1. deref version
(defn funk [r]
(dosync
(println "Start:" @r)
(Thread/sleep 10000)
(println "End:" @r)))
user> (.start (Thread. #(funk r)))
Start: 1
nil
user> (dosync (ref-set r 5))
Start: 5
End: 5
5
Actually, when I type in (dosync (ref-set r 5)), I got 5 soon. After a
few seconds, it prints out the "Start: 5". And after another few
seconds, print out " End: 5". So yes, the funk transcation is redone.
2. Ensure version
(defn funk [r]
(dosync
(println "Start:" (ensure r))
(Thread/sleep 10000)
(println "End:" (ensure r))))
user> (.start (Thread. #(funk r)))
Start: 1
nil
user>user> (dosync (ref-set r 5))
End: 1
5
When I type in the (dosync (ref-set r 5)), that transcation was
blocked. So it is run after the funk finished.
So I can guess that, If you use deref, there can be multipule
transcation running at once. But sometimes transcation must be rerun.
If you use ensure, only one transcation is run. Others are blocked.
Is it true?
Thanks for all your help!
Sincerely,
Jack
On 2月21日, 下午10时26分, Michał Marczyk <michal.marc...@gmail.com> wrote:
On 2月21日, 下午10时26分, Michał Marczyk <michal.marc...@gmail.com> wrote:
Ah yes, that makes more sense :) Thanks again.
> I'm using a newer commit (49a7d6b8e14050a45df5332e768ba6647752215d),
> but this shouldn't make any difference.
No, I only mentioned the commit I was using since I wasn't seeing what
you said you saw. I wouldn't expect there to be a difference in how
it works between versions 1.0, 1.1 and 1.2.
--
Michael Wood <esio...@gmail.com>
Those which try to modify the ref are blocked. Those which only read
it proceed as usual.
> Thanks for all your help!
Sure thing!
Sincerely,
Michał
Yes, the first version I've posted was written precisely so as to
obscure my point. :-)
>> I'm using a newer commit (49a7d6b8e14050a45df5332e768ba6647752215d),
>> but this shouldn't make any difference.
>
> No, I only mentioned the commit I was using since I wasn't seeing what
> you said you saw. I wouldn't expect there to be a difference in how
> it works between versions 1.0, 1.1 and 1.2.
Agreed.
All best,
Michał