Multithreading and mutability in Clojure.


;; atoms are simple mutable items of data
;; changes become immediately visible to all threads,
;; changes are guaranteed to be synchronized by the JVM
(def atom1 (atom 0))

;; dereferencing atom:
(println (str "atom1: " @atom1))

;; changing a value of an atom: pass a function from old value to new one
(swap! atom1 inc)
(println (str "atom1: " @atom1))

(swap! atom1 #(* % 2))
(println (str "atom1: " @atom1))

;; state of an atom can be any value
(def atom2 (atom {:a 1 :b "hello"}))
(println (str "atom2: " @atom2))

(swap! atom2 #(assoc % :b "hi"))
(println (str "atom2: " @atom2))

;; reset, ignore the previous value
(reset! atom1 55)
(println (str "atom1: " @atom1))

;;; Agents:
;; Agents are references that are updated asynchronously:
;; updates happen at a later, unknown point in time, in a thread pool.
(def agent1 (agent 0))
(println (str "agent1: " @agent1))

(send agent1 inc)
(send agent1 inc)
(Thread/sleep 1000)
(println (str "agent1: " @agent1)) ;; may be 0,1,2

;; Refs are coordinated references: we can update multiple references
;; in a transaction
;; Transactions have the following properties: atomicity, consistency, isolation, durability
;; Refs are implemented with STM: Software Transactional Memory
(def account1 (ref 500))
(def account2 (ref 0))

(println (str "account 1: " @account1))
(println (str "account 2: " @account2))

(defn transfer [acc1 acc2 amount]
  (dosync
    (alter acc1 - amount)
    (alter acc2 + amount)))

(transfer account1 account2 300)

(println (str "account 1: " @account1))
(println (str "account 2: " @account2))

(transfer account2 account1 100)

(println (str "account 1: " @account1))
(println (str "account 2: " @account2))

;; you cannot modify a ref without synchronization:
;(alter account1 + 20)

;; if the order of operations doesn't matter, use commute instead of alter,
;; for efficiency:
(defn payday [acc1 acc2 amount]
  (dosync
    (commute acc1 + 500)
    (commute acc2 + 500)))

(println (str "account 1: " @account1))
(println (str "account 2: " @account2))

;;;; Delays, futures and promises: thread handling

;; delay Takes a body of expressions and yields a Delay object that will
;; invoke the body only the first time it is forced (with force or deref/@)
;; Subsequent times the cached result will be returned

;; using delay to get a timestamp:
(def d (delay (System/currentTimeMillis)))
(println @d)
(Thread/sleep 1000)
(println @d)


;; Future: evaluates a piece of code in another thread.
;; Returns immediately  (it never blocks the current thread).
(def long-comp (future (reduce + (map inc (range  100000000)))))
(realized? long-comp)
(println @long-comp)


;; Promises: don't have code to evaluate, are "holders"
;; for a value placed via deliver function
(def p (promise))
(realized? p)
(deliver p 42)
(realized? p)
(+ @p 2)

CSci 4651 course web site.

The views and opinions expressed in this page are strictly those of the page author. The contents of this page have not been reviewed or approved by the University of Minnesota.