Refs and Agents
Prerequisites: Atoms
Atoms manage one independent value. Clojure has two more reference types for other situations.
| Type | Coordinated? | Synchronous? | Use for |
|---|---|---|---|
| Atom | no | yes | independent state (most cases) |
| Ref | yes | yes | several values that must change together |
| Agent | no | no | background, sequential updates |
Refs and transactions
(def checking (ref 100))
(def savings (ref 50))
(defn transfer! [from to amount]
(dosync
(when (< @from amount)
(throw (ex-info "Insufficient funds" {:available @from})))
(alter from - amount)
(alter to + amount)))
(transfer! checking savings 30)
(println @checking @savings)
(try
(transfer! checking savings 500)
(catch Exception e (println (ex-message e) (ex-data e))))
(println @checking @savings (+ @checking @savings))
70 80
Insufficient funds {:available 70}
70 80 150
dosyncruns a transaction using Clojure's software transactional memory (STM). All changes inside happen together or not at all — if an exception is thrown, nothing changes.alterapplies a function to a ref inside a transaction;ref-setsets it;commuteis for order-independent updates like counters.- If two transactions conflict, one is retried automatically, so keep
side effects (printing, I/O) out of
dosync.
Many concurrent transfers
(def accounts (vec (repeatedly 5 #(ref 1000))))
(let [jobs (doall
(for [i (range 1000)]
(future
(dosync
(let [from (accounts (mod i 5))
to (accounts (mod (* 3 i) 5))]
(alter from - 1)
(alter to + 1))))))]
(run! deref jobs))
(println (map deref accounts))
(println "total:" (reduce + (map deref accounts)))
(shutdown-agents)
(1000 1000 1000 1000 1000)
total: 5000
A thousand concurrent transactions, no locks, and the total is always exactly 5,000.
Agents
(def log (agent []))
(dotimes [i 5]
(send log conj (str "event " i)))
(await log) ; wait for queued actions
(println @log)
(def failing (agent 0))
(send failing / 0)
(Thread/sleep 100)
(println (type (agent-error failing)))
(shutdown-agents)
[event 0 event 1 event 2 event 3 event 4]
java.lang.ArithmeticException
sendqueues a function to run on the agent's value in the background; actions for one agent run one at a time, in order.send-offis the same but for blocking work like I/O.- A failed action puts the agent in an error state (
agent-error); userestart-agentor an:error-handlerto recover. - Call
(shutdown-agents)at the end of a program that used agents or futures, or the JVM waits about a minute for idle threads before exiting.
In practice, atoms cover most needs; refs are rare, and agents are
mostly replaced by future, executors, or core.async (Level 3).