The alter and commute are two ways to update a Ref type in a trasaction in Clojure. This post Clojure concurrency: Ref type and coordinate change example demonstrate how to use Ref type in a classic way.

How alter works?

The alter is pretty clear and easy to understand, it do the update operation based on the view of the world at the start of the transaction, this world is so called snapshot, the update operation itself has no contention, you can think of the operation itself is done in a way that the current thread is the only one which do the change. Thats STM all about, no lock, no race condition.

If you understand CAS(compare and set) based nonblocking atomic operation, the STM transaction is the same thing. At the end of transaction, when doing commit, it can be success or failure. The failed one get retried. But no one is blocked or contend with others. In such an environment, one thread basically don't have to care about other threads, it just do the transaction, if succeed then success, if failed, just retry.

The reason to cause a transaction fail is that other threads already changed the world before current transaction commit, for example, when transaction start, a variable has value 1, current transaction increase it by one, if another thread already changed it to 2, then current transaction can not succeed, because otherwise current update operation has no effect. The result is wrong because two threads add 1 to the same variable, but final value is 2. So current thread retry, get a new snapshot of the world, now the variable has value 2, do increasing and commit, the value will be 3.

alter and commute mostly the same

First and foremost, alter and commute are mostly the same, so if you know alter you already know 90% about commute. If you replace all alter with commute, the final result of the Ref value will be the same.

Out of order commit and in order commit

The difference is how they do commit, for commute commit is out of order and alter is in order. What the order mean? If commit is in order, from the update operations to commit there should be no other transactions doing a successful commit. So in a successfully committed transaction, the updated value is the same as the committed value. If found that using the update value will cause inconsistency when committing, the whole transaction is retried. For commute, when found such inconsistency, it will not retry, it will get the most current value and do update operations again based on most current value. Thus it may out of order. The in transaction value may different with the commit value.

Because there are no retries, commute allows more concurrency than alter, the trade off is the in-transaction value is not much useful. If using alter, you can determine the oder of all successful committed transactions, which one complete first, which one complete second and so on. For commute, its impossible, the commit oder is undecidable.

Code example

(defn tid [] (.getId (Thread/currentThread)))
(defn debug
  [ msg ]
  (print (str msg (apply str (repeat (- 35 (count msg)) \space))  " tid: " (tid)  "\n"))
(def a (ref 1))
(defn do-transaction [_]
    (alter a + 1)
    (debug (str "in transaction value: " @a))
(doseq [dummyagent (range 1 6)]
  (send-off (agent  dummyagent) do-transaction)

From the output

in transaction value: 2             tid: 12
in transaction value: 3             tid: 15
in transaction value: 3             tid: 14
in transaction value: 3             tid: 11
in transaction value: 4             tid: 15
in transaction value: 5             tid: 13
in transaction value: 6             tid: 14

We can see the commit order is thread id 12 11 15 13 14, thread 15 and 14 retried. If we replace alter with commute.

(defn do-transaction [_]
    (commute a + 1)
    (debug (str "in transaction value: " @a))

The output will be

in transaction value: 2             tid: 17
in transaction value: 2             tid: 20
in transaction value: 2             tid: 19
in transaction value: 2             tid: 18
in transaction value: 2             tid: 16
user=> @a

Each transaction run only once, at start of all transactions, they see the same snapshot, but the order of committing can be any order.