(commute ref fun & args)
Must be called in a transaction. Sets the in-transaction-value of ref to: (apply fun in-transaction-value-of-ref args) and returns the in-transaction-value of ref. At the commit point of the transaction, sets the value of ref to be: (apply fun most-recently-committed-value-of-ref args) Thus fun should be commutative, or, failing that, you must accept last-one-in-wins behavior. commute allows for more concurrency than ref-set.
user=> (def counter (ref 0))
#'user/counter
;; deciding whether to increment the counter takes the terribly long time
;; of 100 ms -- it is decided by committee.
user=> (defn commute-inc! [counter]
(dosync (Thread/sleep 100) (commute counter inc)))
#'user/commute-inc!
user=> (defn alter-inc! [counter]
(dosync (Thread/sleep 100) (alter counter inc)))
#'user/alter-inc!
;; what if n people try to hit the counter at once?
user=> (defn bombard-counter! [n f counter]
(apply pcalls (repeat n #(f counter))))
#'user/bombard-counter!
;; first, use alter. Everyone is trying to update the counter, and
;; stepping on each other's toes, so almost every transaction is getting
;; retried lots of times:
user=> (dosync (ref-set counter 0))
0
user=> (time (doall (bombard-counter! 20 alter-inc! counter)))
"Elapsed time: 2007.049224 msecs"
(3 1 2 4 7 10 5 8 6 9 13 14 15 12 11 16 17 20 18 19)
;; note that it took about 2000 ms = (20 workers * 100 ms / update)
;; now, since it doesn't matter what order people update a counter in, we
;; use commute:
user=> (dosync (ref-set counter 0))
0
user=> (time (doall (bombard-counter! 20 commute-inc! counter)))
"Elapsed time: 401.748181 msecs"
(1 2 3 4 5 9 10 6 7 8 11 15 13 12 14 16 19 17 18 20)
;; notice that we got actual concurrency this time.
; Note that commute will ALWAYS run the update function TWICE.
; Example courtesy of "Clojure for the Brave and True"
; https://github.com/flyingmachine/brave-clojure-web
(defn sleep-print-update
[sleep-time thread-name update-fn]
(fn [state]
(Thread/sleep sleep-time)
(println (str thread-name ": " state))
(update-fn state)))
(def counter (ref 0))
(future (dosync (commute counter (sleep-print-update 100 "Commute Thread A" inc))))
(future (dosync (commute counter (sleep-print-update 150 "Commute Thread B" inc))))
; printed output is:
Commute Thread A: 0 ; (after 100ms)
Commute Thread B: 0 ; (after 150ms)
Commute Thread A: 0 ; (after 200ms)
Commute Thread B: 1 ; (after 300ms)