Day 3 focused on Clojure's support for concurrency. A big part of Clojure's concurrency story is support for the concept of a software transactional memory (STM). Much like a database transaction, modifications to mutable state are made within a transaction that the STM guarantees will be atomic, consistent and isolated. On top of the STM, Clojure provides a number of constructs to deal with shared across threads of execution, in particular we are given: atoms, refs and agents. Definitions of the differences are linked to, but at a basic level, all of these essentially 'wrap' a piece of data and allow you to change it according to a given set of semantics. You can provide additional semantics by providing a validator function that checks new values generated for a given piece of wrapped data.

Clojure's support for concurrency goes hand in hand with its preference for pure functions, for example, within the STM if a two competing threads end up trying to mutate a piece of data at the same time, one of them may be retried (the STM does not use locks to prevent the two from running concurrently but uses something akin to MVCC). Thus if a function has side effects these may execute more than one time, so one should only use pure functions when modifying mutable state. On to the exercises.

(ns day-3
(:use clojure.contrib.pprint))
;Q1 use refs to create a vector of accounts in memory. Create debit and credit
; functions to change the balance of an account
; Note: I'm going to use a map instead of a vector so that accounts can have names
(def bank (ref { }))
(defn make-account [bank account-name balance]
(dosync
(alter bank (conj {account-name, balance}))))
;Use a let binding to hold the newly calculated balance before altering the ref
(defn debit-account [bank account-name amount]
(dosync
(let
[new-balance (- (get @bank account-name) amount)]
(alter bank assoc account-name new-balance))))
(defn credit-account [bank account-name amount]
(dosync
(let
[new-balance (+ (get @bank account-name) amount)]
(alter bank assoc account-name new-balance))))
;Create som
view raw gistfile1.clj hosted with ❤ by GitHub

In the snippet above, the bank is a 'ref' this means we can use transactions marked by dosync to modify account balances without worrying about race conditions or inconsistency (I could have used atoms as well), we use the functions that clojure provides to mutate data within a tractions such as alter and ref-set. Alter basically takes a ref and a function whose return value will be the new value stored in that ref.

The next exercise was to write a solution to the sleeping barber problem (I had also tackled this in Io)

;The sleeping barber problem http://en.wikipedia.org/wiki/Sleeping_barber_problem
; Q. Calculate how many haircuts can be done in 10 seconds
; One barber, one barber chair
; Three chairs in waiting room
; Customers arrive at random intervals 10-30ms apart
; Haircuts take 20ms
; (ns day-3
; (:use clojure.contrib.pprint))
;Model the chair and waiting room as vectors wrapped in refs
(def barber-chair (ref []))
(def waiting-room (ref []))
(def num-cuts (ref 0))
(def num-turnaway (ref 0))
;forward reference foe functions that call each other. haven't seen that in a while :)
(declare cut-hair get-next-customer)
;Sleeps for 20ms then vacates the barber-chair
;Will also check the waiting room after doing a haircut
(defn cut-hair []
(do
(Thread/sleep 20)
(dosync
(ref-set barber-chair [])
(alter num-cuts + 1)
(get-next-customer))))
(defn get-next-customer []
(if (not (empty? @waiting-room)) ;i don't think this check needs to be in the transaction
(do
(dosync
(let
[next-customer (first @waiting-room)]
(ref-set barber-chair [next-customer])
(alter waiting-room rest)))
(cut-hair)))) ; note this call to cut hair in the same thread
(defn customer-arrive [customer]
;take the barber chair or a seat in the waiting room
(dosync
(if (empty? @barber-chair)
(do
(ref-set barber-chair [customer]) ;put the customer in the chair
;use of future-call to run this in its own thread
;note: this may be seem technically incorrect (or maybe unorthodox) as the cut-hair
;function would use a different thread each time called from this location.
;However this function will not be called if the barber is already cutting hair. i.e.
;it is equivalent to waking the barber up.
;General clojure question, do the things backing a future need to be cleaned up? they hold onto
;their return value until dereferenced, so if never deference the return val, then what?
(future-call cut-hair))
(if (< (count @waiting-room) 3)
(alter waiting-room concat [customer]) ;put the customer in the waiting room
(alter num-turnaway + 1)))))
;To send the customers to the barber at different intervals
;we use the future macro, this will run the body passed to the
;macro in its own thread. I believe this macro is a wrapper around future-call
(def send-customers
(future
(while true
(do
(Thread/sleep (+ 10 (rand-int 20)))
(customer-arrive :cust) ;they could all have unique names but whatevs
))))
(Thread/sleep (* 10 1000))
(println @num-cuts)
(println @num-turnaway)
(shutdown-agents)

I hope this solution is correct. One thing to notice is how short this is (the code above is heavily commented). Another thing here is that we modify multiple refs within a given transaction. That is in fact, as i understand it, the main difference between refs and atoms. Modifications to refs within a transaction are coordinated, at the end of the transaction the changes to all the refs within it have to succeed for the transaction as a whole to succeed. Changes to atoms are independent and do not need to be in a transaction.

Also shown here is the future macro (as well as the future-call function), which is basically a way of getting a function to run on another thread. You can then check on the status of the return val from the future or block until you get a value back. Here it is just used to send customers to the barber for 10 seconds.

One thing i couldn't get to work that I would have liked, was to get the output of printlns within the body of a future to show; whenever i put some printlns within a future (or even the transactions) I wouldn't get any console output. Not sure what I need to do to get that to work.

I would also like to try and do this again and use agents to model the solution, as i didn't use agents at all and don't really know much about them. In anycase it was a fun set of exercises and I feel I got a good intro to clojure's concurrency support though there is a lot more to be seen I think. Overall I enjoyed the Clojure chapter and do want to dig into the language a bit more.

Other than that I also picked up "Programming Clojure" by Stuart Halloway as it was on sale, and its really quite good and very readable.