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.