CodeToLive

Clojure Concurrency

Clojure provides powerful tools for concurrent programming while maintaining immutability as the default. Its concurrency model is designed for correctness and simplicity.

Reference Types

Clojure offers four primary reference types for managing state:

1. Atoms


(def counter (atom 0))

@counter     ; => 0 (dereference)
(reset! counter 1) ; => 1
(swap! counter inc) ; => 2
                

2. Refs


(def account-a (ref 100))
(def account-b (ref 200))

(dosync
  (alter account-a - 50)
  (alter account-b + 50))

@account-a ; => 50
@account-b ; => 250
                

3. Agents


(def log-agent (agent []))

(send log-agent conj "Event 1")
(send log-agent conj "Event 2")

@log-agent ; => ["Event 1" "Event 2"]
                

4. Vars


(def ^:dynamic *db-host* "localhost")

(binding [*db-host* "test-server"]
  (println *db-host*)) ; => "test-server"
                

Threading Basics

Creating Threads


(.start (Thread. #(println "Running in thread")))
                

Thread Pools


(import 'java.util.concurrent.Executors)

(def pool (Executors/newFixedThreadPool 4))

(.execute pool #(println "Task running in pool")))
                

Futures


(def long-calculation 
  (future 
    (Thread/sleep 5000)
    (+ 1 2 3)))

@long-calculation ; blocks until done => 6
                

Software Transactional Memory (STM)

Clojure's STM provides ACID transactions for coordinated changes:


(def balance (ref 100))

(defn transfer [from to amount]
  (dosync
    (alter from - amount)
    (alter to + amount)))

(transfer balance (ref 50) 30)
@balance ; => 70
                

Transaction Retries


(defn retry-example [r]
  (dosync
    (println "Attempting transaction")
    (alter r inc)
    (when (< @r 5)
      (retry))))

(def retry-ref (ref 0))
(retry-example retry-ref) ; Prints 5 attempts
                

Asynchronous Programming

core.async


(require '[clojure.core.async :as async :refer [go chan !]])

(def ch (chan))

(go (let [value (! ch 42)) ; Prints "Received: 42"
                

Channels


(def ch (chan 10)) ; Buffered channel

(go (doseq [i (range 10)]
      (>! ch i)
      (async/close! ch)))

(go (loop []
      (when-let [value (

Concurrency Patterns

Worker Pool


(def work-chan (chan 100))
(def result-chan (chan))

(dotimes [i 5]
  (go (while true
        (let [task (! result-chan (perform-task task))))))

(doseq [task tasks]
  (>!! work-chan task))
                

Fan-In/Fan-Out


(defn fan-out [in-ch out-chs]
  (go (while true
        (let [value (! ch value))))))

(defn fan-in [out-ch in-chs]
  (go (while true
        (let [[value ch] (async/alts! in-chs)]
          (>! out-ch value)))))
                

Performance Considerations

  • Atoms: Best for independent, uncoordinated updates
  • Refs: For coordinated synchronous changes
  • Agents: For asynchronous, independent updates
  • core.async: For complex asynchronous workflows

Next Steps

Continue learning with: