I think I understand the basic difference between the ideas of commute
and alter
within a Clojure transaction.
alter
essentially 'locks' the identity from the start to the end of the transaction so that multiple transactions must execute sequentially.
commute
only applies the lock to the actual change of values for the identity, so that other actions in the transaction may run at different times and with different views of the world.
But I am confused about something. Let's define a function with side-effects and a ref to act on:
(defn fn-with-side-effects [state]
(println "Hello!")
(inc state))
(def test-counter (ref 0))
Now if we use alter, we see expected behavior:
user=> (dosync (alter test-counter fn-with-side-effects))
Hello!
1
But if we use commute:
user=> (dosync (ref-set test-counter 0))
0
user=> (dosync (commute test-counter fn-with-side-effects))
Hello!
Hello! ; hello is printed twice!
1
So in the commute version, the function clearly only modifies the ref once because the final value is 1. But side-effects of the modifier function are executed twice. Why does this happen?
I figured it out.
This happens because commute functions are always executed twice.
Commute allows for more potential concurrency than alter because it doesn't lock the identity for the entire duration of the transaction.
Instead, it reads the identity's value once at the beginning of the transaction and when the commute operation is called it returns the commute function applied to THIS VALUE.
It's entirely possible that this value is now out of date because some other thread could have changed it some time between the start of the transaction and the execute of the commute function.
However, integrity is maintained because the commute function is executed AGAIN at commit time when it actually modifies the ref.
This site has a very clear explanation of the difference: http://squirrel.pl/blog/2010/07/13/clojure-alter-vs-commute/
In fact, when commute is called it instantly returns result of running the function on the ref. At the very end of transaction it performs the calculation again, this time synchronously (like alter) updating the ref. That’s why eventually the counter value is 51 even though the last thread printed 45.
So be careful if your commute function has side-effects because they will be executed twice!!