Clojure concurrency - part 3

May 20, 2010

[Cloure concurrency, part 2]

In my previous post, I had shown how Clojure atoms help to solve race conditions which occur during read-modify-write of a single entity. But there are situations in which you will be required to act on more than one data structure in your code (from multiple threads). Let's look at a very simple (and somewhat artificial) example.

Let's say you have two atoms with initial values 0 and 1:

(def A (atom 0))
(def B (atom 1))

We have two threads, T1 and T2; both executing an infinite loop. This is what T1 does in each iteration: it sets the value of A to N, a random integer and sets the value of B to N + 1 (the value of N keeps changing every iteration).

Now, let's define an arbitrary `consistency' requirement on the values of A and B - whenever a `reader' thread T2 looks at A and B, the difference B - A should always be 1. If this is not the case, the reader thread reports an error and terminates.

Here is the idea expressed in code:

(import 'java.util.Random)

(def N1 (atom 0))
(def N2 (atom 1))

(defn writer []
  (let [n (.nextInt (Random.))]
      (reset! N1 n)
      (reset! N2 (inc n))
      (println "writer...")

(defn reader []
  (if (not (= 1 (- @N2 @N1)))
      (println "reader ...")

(defn start-all-threads []
  (let [t1 (Thread. writer)
	t2 (Thread. reader)]
      (.start t1)
      (.start t2)
      (.join t1)
      (.join t2))))


If you run the code as a script, you will note that sooner or later, the reader thread will stop because it has discovered a set of values for N1 and N2 which does not satisfy the constraint that N2 - N1 equals 1. Why does this happen? Imagine that the current values of N1 and N2 are 10 and 11. The next iteration of the writer thread changes N1 to say 130 - before the writer changes N2 to 131, what if the reader examines both N1 and N2 - it will see a combination of values 130 and 11 which does not satisfy the constraint that N2 - N1 equal to 1. Atoms are of no help in preventing such a situation from arising. We need a more complex mechanism called `Refs' in Clojure terminology; that will be the topic of the next post.

[Go to Code Clojure home] [Follow me on Twitter] [Go to home]