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),
One thing that wasn’t obvious to me after reading the article was the difference between
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
(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
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.