Do I need to add "dosync" when I read the ref in this case

93 views
Skip to first unread message

Jack Fang

unread,
Feb 20, 2010, 9:07:34 AM2/20/10
to Clojure
Hi,all

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


ataggart

unread,
Feb 21, 2010, 2:24:08 AM2/21/10
to Clojure
On Feb 20, 6:07 am, Jack Fang <jack.fang1...@gmail.com> wrote:
>    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?  

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?

Michael Wood

unread,
Feb 21, 2010, 3:37:06 AM2/21/10
to clo...@googlegroups.com

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>

Jarkko Oranen

unread,
Feb 21, 2010, 5:55:56 AM2/21/10
to Clojure
>
> 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.

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.

Michał Marczyk

unread,
Feb 21, 2010, 6:47:46 AM2/21/10
to clo...@googlegroups.com
On 21 February 2010 09:37, Michael Wood <esio...@gmail.com> wrote:
> (defn print-info []
>  (let [[src dst] (dosync
>                    (ensure source-account)
>                    (ensure dest-account)
>                    [@source-account @dest-account])]
>    (println src)
>    (println dst)))

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ł

Michael Wood

unread,
Feb 21, 2010, 7:47:07 AM2/21/10
to clo...@googlegroups.com

OK, so ensure is not necessary in this case. Thanks.

--
Michael Wood <esio...@gmail.com>

Michael Wood

unread,
Feb 21, 2010, 8:13:45 AM2/21/10
to clo...@googlegroups.com
On 21 February 2010 13:47, Michał Marczyk <michal....@gmail.com> wrote:
> On 21 February 2010 09:37, Michael Wood <esio...@gmail.com> wrote:
>> (defn print-info []
>>  (let [[src dst] (dosync
>>                    (ensure source-account)
>>                    (ensure dest-account)
>>                    [@source-account @dest-account])]
>>    (println src)
>>    (println dst)))
>
> 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.

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>

Michał Marczyk

unread,
Feb 21, 2010, 8:26:33 AM2/21/10
to clo...@googlegroups.com
On 21 February 2010 14:13, Michael Wood <esio...@gmail.com> wrote:
> That does demonstrate the difference (or a difference), but I don't
> get 3 printlns.

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ł

philipp siegmantel

unread,
Feb 21, 2010, 1:04:33 AM2/21/10
to clo...@googlegroups.com
You do not have to. Everything inside your dosync-block will happen atomically, meaning print-info will either see the values of source-account and dest-account before your transaction happened or after it's done his work.

--
Ph. Siegmantel

2010/2/20 Jack Fang <jack.f...@gmail.com>
--
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

Jack Fang

unread,
Feb 21, 2010, 10:49:52 AM2/21/10
to Clojure
I try my code on my machine.

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:

Jack Fang

unread,
Feb 21, 2010, 10:58:22 AM2/21/10
to Clojure
Thanks for you all

On 2月21日, 下午10时26分, Michał Marczyk <michal.marc...@gmail.com> wrote:

Michael Wood

unread,
Feb 21, 2010, 2:58:26 PM2/21/10
to clo...@googlegroups.com

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>

Michał Marczyk

unread,
Feb 22, 2010, 1:07:09 PM2/22/10
to clo...@googlegroups.com
On 21 February 2010 16:49, Jack Fang <jack.f...@gmail.com> wrote:
> 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?

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ł

Michał Marczyk

unread,
Feb 22, 2010, 1:07:49 PM2/22/10
to clo...@googlegroups.com
On 21 February 2010 20:58, Michael Wood <esio...@gmail.com> wrote:
> Ah yes, that makes more sense :)

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ł

Reply all
Reply to author
Forward
0 new messages