Software Transactional Memory in Clojure (alter vs. commute)

One of the selling points of Clojure is its excellent support for multithreading. Among other features, it has a very intuitive yet powerful implementation of Software Transactional Memory. There’s a nice introduction on it in R. Mark Volkmann’s famous article.

One notable feature of STM is a ref – shared binding which can only be modified inside transactions. Transactions are wrapped in (dosync) and behave similarly to database transactions. Value of a ref can be set in 3 ways: ref-set (obvious setter), commute and alter.

One thing that wasn’t obvious to me after reading the article was the difference between alter and commute. They both take a ref and a function, set the ref to result of running the function on previous value of the ref, and return the new value.

I decided to test its behavior with this simple experiment. Define an integer counter. Start 50 threads that increase this counter with alter and print the new value. After all threads finish, print the counter. Then repeat with commute instead of alter.

Here’s the sample with alter:

(def my_num (ref 0))

(defn inc_alter []
  (dosync
    (try
      (println "Alter: " (alter my_num inc)) 
      (catch Throwable t 
        (do 
          (println "Caught " (.getClass t)) 
          (throw t))))))

(defn test_alter []
  (let [threads (for [x (range 0 50)] (Thread. #(inc_alter)))]
    (do
      (println "---- Alter ----")
      (doall (map #(.start %) threads))
      (doall (map #(.join %) threads))
      (println "---- After loop: ----") 
      (inc_alter))))

(test_alter)

The output can look like:

---- Alter ----
Alter:  1
Alter:  2
Alter:  3
...
Alter:  47
Caught  clojure.lang.LockingTransaction$RetryEx
Alter:  48
Caught  clojure.lang.LockingTransaction$RetryEx
Alter:  49
Caught  clojure.lang.LockingTransaction$RetryEx
Caught  clojure.lang.LockingTransaction$RetryEx
Alter:  50
---- After loop: ----
Alter:  51

Explanation: when alter detects that the ref has been changed in another concurrent transaction, the whole transaction is rolled back. Then it is automatically retried. That’s fairly intuitive and straightforward.

When we replace alter with commute, it’s quite a different story. The output can look like:

---- Commute ----
Commute:  1
Commute:  2
Commute:  3
...
Commute:  43
Commute:  45
Commute:  45
Commute:  45
Commute:  45
---- After loop: ----
Commute:  51

The modification is commutative. No transaction is ever rolled back. But it also means that inside the transaction a stale value can be used, what is the reason for getting “45” several times.

It doesn’t work exactly like I described earlier (get value, commute, set value, return). 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.

By the way, have a look at the sample code and think about how much it would take to implement it your Java or C#. Not only is it multithreaded, but also synchronized, transactional (with ACID or at least ACI), serializable with auto-retry, and so on. So much going on in just 20 lines.

Leave a Reply

Your email address will not be published. Required fields are marked *

Spam protection by WP Captcha-Free