or Clojure’s concurrency agents – a simple example
Clojure has some fantastic concurrency idioms. Agents are but one example of what’s available to the JVM lisper — alongside refs, atoms and vars — that make concurrent programming very easy.
I thought I’d articulate my understanding to help embed the knowledge but to also provide a simple example for newcomers coming to Clojure.
Clojure’s agent constructs are its way of providing asynchronous read/writes that guarantee visibility across threads but give you the option of not “caring” when those events occur. Put simply, agents are a wrapper for values that can be written/read by multiple “clients” with no guarantee of order. An example would be a fork/join solution: you divide the problem into “bitesize” parts that can then be combined at a common barrier point for combination and utilisation elsewhere.
Here’s how you define an agent:
(def a (agent 0)) ; define an agent with an initial value of 0
To read an agent’s value, we just dereference it:
(println @a) ; or (deref a)
To write, you send a function to apply to the value in the agent, like so:
(send a inc) ; apply inc to a
which results in increment of a’s value, to 1.
Clojure’s documentation calls the “inc” in the (send) call above the “action-fn”:
user=> (doc send)
([a f & args])
Dispatch an action to an agent. Returns the agent immediately.
Subsequently, in a thread from a thread pool, the state of the agent
will be set to the value of:
(apply action-fn state-of-agent args)
Executing asynchronously with agents
As stated, agents allow you to execute something asynchronously (in a thread pool managed by Clojure) whose execution order you don’t require control over. Every time you
(send) to an agent you can effectively assume the function to apply will be executed as part of a queue (and such queueing is done so synchronously, though the execution is not).
To demonstrate this asynchronous nature, we can extend our previous example to utilise the
Let’s define a new agent,
(def b (agent 10000))
whose initial value is 10000.
Now, let’s have a function that will A) utilise
b‘s current value to do something and B) also change its state.
Thread/sleep and the value of
b we can satisfy A above and simply use
(inc) to change
(defn bond  (send b #(do (Thread/sleep %1) (inc %1))))
So what do we have?
On paper, if we call bond, we’d wait for
@b milliseconds and then increment b by one. Lovely and simple. Defining the logic above in a function
(bond) allows us to easily demonstrate the nature of agents and the
(send) function: that
(send) will queue the action-fn (the function we give to apply to the agent) and return immediately, and that the action-fn will execute in a seperate thread and be applied to the agent.
Let’s prove that
(bond) is doing what we expect by calling it:
user=> @b ; prove b's value is as-initialised
user=> (bond) ; invoke bond which will (send) to the agent
user=> @b ; derefence (access the value of) b to prove it hasn't changed
No change thus far…Lest we wait 10 seconds…
Aha! The agent’s value has changed.
(bond) has accomplished his mission!
If we wanted to wait until the action-fn had finished — e.g. you’re implementing a fork/join solution — we can simply use
user=> (def b (agent 10000))
user=> (defn bond  (send b #(do (Thread/sleep %1) (inc %1))))
user=> (await b)