You are here:   ArielOrtiz.com > Programming Languages > Clojure Concurrency API

[ Printer friendly page ]

The following notes are a subset of the Clojure and API for clojure.core documentation, plus some examples.

Clojure Concurrency API

Quick Links to Contents

Atoms

Atoms allow changing a single value. They are updated synchronously (immediately) and are uncoordinated (independent from each other).

Function Description
(atom x & options)

Creates and returns an atom with an initial value of x. options can be:

:validator validate-fn

validate-fn must be nil or a side-effect-free function of one argument, which will be passed the intended new state on any state change. If the new state is unacceptable, validate-fn should return a false value or throw an exception.

(deref a) Returns the current state of atom a. Equivalent to: @a
(reset! a x) Sets the value of atom a to x, ignoring its previous value. Returns x.
(swap! a f & args) Atomically swaps the value of atom a to: (apply f @a args). Note that f may be called multiple times, and thus should be free of side effects. Returns the value that was swapped in.

Examples:

(def my-atom (atom 5))

(deref my-atom)               ⇒ 5
@my-atom                      ⇒ 5
(reset! my-atom 0)            ⇒ 0
@my-atom                      ⇒ 0
(swap! my-atom inc)           ⇒ 1
@my-atom                      ⇒ 1

;;; This atom will only accept numeric values.
(def my-other-atom (atom 42 :validator number?)) 

@my-other-atom                ⇒ 42
(reset! my-other-atom 0)      ⇒ 0
(swap! my-other-atom inc)     ⇒ 1
(reset! my-other-atom "hi")   ⇒ IllegalStateException
@my-other-atom                ⇒ 1
(swap! my-other-atom number?) ⇒ IllegalStateException
@my-other-atom                ⇒ 1

Refs

A ref is a managed reference that allows for synchronous (immediate) and coordinated (dependent on other) changes to mutable data.

Refs ensure safe shared use of mutable storage locations via a software transactional memory (STM) system. Refs are bound to a single storage location for their lifetime, and only allow mutation of that location to occur within a transaction.

Clojure transactions ensure that all actions on refs are atomic, consistent, and isolated. Atomic means that every change to refs made within a transaction occurs or none do. Consistent means that each new value can be checked with a validator function before allowing the transaction to commit. Isolated means that no transaction sees the effects of any other transaction while it is running. Another feature common to STMs is that, should a transaction have a conflict while running, it is automatically retried.

The Clojure STM uses multiversion concurrency control (MVCC) which means:

Function/Macro Description
(alter r f & args) Sets the in-transaction-value of ref r to: (apply f @r args). Returns the in-transaction-value of r. Must be called in a transaction.
(deref r) Within a transaction, returns the in-transaction-value of ref r, else returns the most recently committed value of r. Equivalent to: @r
(dosync & exprs) Runs the exprs (in an implicit do) in a transaction. Starts a transaction if none is already running on this thread. Any uncaught exception will abort the transaction and flow out of dosync. The exprs may be run more than once, but any effects on refs will be atomic.
(ref x & options)

Creates and returns a ref with an initial value of x. options can be:

:validator validate-fn

validate-fn must be nil or a side-effect-free function of one argument, which will be passed the intended new state on any state change. If the new state is unacceptable, validate-fn should return a false value or throw an exception.

(ref-set r val) Sets the value of ref r to val. Returns val. Must be called in a transaction.

Examples:

(def x (ref 10))

;;; This ref will only accept numeric values.
(def y (ref 16 :validator number?))

(defn atomic-multiply [a b factor]
  (dosync
    (alter a * factor)
    (alter b * factor)))

(defn swap-ref-values [a b]
  (dosync 
    (let [temp @a]
      (ref-set a @b)
      (ref-set b temp))))

@x                         ⇒ 10
@y                         ⇒ 16      
(atomic-multiply x y 2)    ⇒ 32
@x                         ⇒ 20
@y                         ⇒ 32
(swap-ref-values x y)      ⇒ 20
@x                         ⇒ 32
@y                         ⇒ 20
(dosync (alter y number?)) ⇒ IllegalStateException

Futures

Futures represent asynchronous computations. They are a way to get code to run in another thread and obtain the result at a later moment.

Function Description
(deref f) Return the result of the future f. Will block if computation not complete. Equivalent to: @f
(future & body) Takes a body of expressions and yields immediately a future object that will invoke the body in another thread, and will cache the result and return it on all subsequent calls to deref. If the computation has not yet finished, calls to deref will block.
(future? x) Returns true if x is a future, otherwise returns false.
(future-done? f) Returns true if future f has finished its computations, otherwise returns false.
(future-cancel f) If possible, cancels the future f. Returns true if the future got cancelled, or false otherwise.
(future-cancelled? f) Returns true if future f has been cancelled, otherwise returns false.

Examples:

(def f (future (+ 1 1)))
(future? f)           ⇒ true
(future-done? f)      ⇒ true
(future-cancelled? f) ⇒ false
(future-cancel f)     ⇒ false
@f                    ⇒ 2

;;; This future will take one minute to execute.
(def x (future (Thread/sleep 60000) 42))
(future? x)           ⇒ true
(future-done? x)      ⇒ false
(future-cancelled? x) ⇒ false
(future-cancel x)     ⇒ true
(future-cancelled? x) ⇒ true
@x                    ⇒ CancellationException

Parallel Functions

Function/Macro Description Examples
(pcalls & fns) Executes the no-argument fns in parallel, returning a lazy sequence of their values. (pcalls #(+ 1 2) #(* 3 4) #(/ 5 6))
⇒ (3 12 5/6)
(pmap f coll & colls) Like map, except f is applied in parallel. Semi-lazy in that the parallel computation stays ahead of the consumption, but doesn't realize the entire result unless required. Only useful for computationally intensive functions where the time of f dominates the coordination overhead. (pmap
  #(Math/pow %1 %2)
  [2 3 4 5] [3 2 1 0])
⇒ (8.0 9.0 4.0 1.0)
(pvalues & exprs) Returns a lazy sequence of the values of the exprs, which are evaluated in parallel. (pvalues (+ 1 2) (* 3 4) (/ 5 6))
⇒ (3 12 5/6)