Clojure concurrency - part 4
May 23, 2010
A Clojure `Ref' is the solution to the problem discussed in the last post. Here is how you create a `ref':
(def p (ref 0))
The value of a ref can be changed by `ref-set':
(ref-set p 1)
But this doesn't work! We get an exception. The idea is that the ref can be changed only from within a `transaction':
(dosync (ref-set p 1))
What is so special about a `transaction'? Let's rewrite the program in the previous post to use ref's and transactions.
(import 'java.util.Random) (def N1 (ref 0)) (def N2 (ref 1)) (defn writer  (let [n (.nextInt (Random.))] (dosync (ref-set N1 n) (ref-set N2 (inc n))) (println "writer...") (recur))) (defn reader  (if (not (= 1 (dosync (- @N2 @N1)))) nil (do (println "reader ...") (recur)))) (defn start-all-threads  (let [t1 (Thread. writer) t2 (Thread. reader)] (.start t1) (.start t2) (.join t1) (.join t2))) (start-all-threads)
If you run the code, you will see that both the reader and writer keep running happily for ever ...
The reader computes N2 - N1 within a transaction and the writer updates the values within another transaction. The Clojure transaction semantics guarantees that transactions are Atomic, Consistent and Isolated. The transaction in the reader thread is completely isolated from the transaction in the writer thread - the reader transaction, once it has started, does not see any of the change made by the writer transaction. This ensures that the reader keeps running for ever.
At this point, you might wonder as to what is so special about transactions; the same effect could have been achieved using an explicit lock ... well the fact that we are *not* using any explicit locks itself is a big win! Traditional concurrent programming using locks/mutexes is horribly complicated - just as automatic memory management has liberated programmers from the complexities associated with allocating/freeing memory, it is hoped that transactional memory will ease the pain associated with building large scale concurrent systems.