Clojure provides a lot of different facilities to handle concurrency. One of
these are Vars. They can be used to store thread-local information, eg. with
binding
. A different thread cannot see the value „modified“ with binding
.
And exactly there lies the rub…
Let's start with some setup, which we can use later on.
(def *increment* 1)
(defn incr
[x]
(+ x *increment*))
This is basically a function similar to inc
which increments a given number.
Compared to inc
it provides a knob by which we can change the increment step.
So. Now let's try our function.
user=> (map incr [1 2 3])
(2 3 4)
Ok. Nothing exciting so far. But now let's play with our knob. We use binding
to change the value of *increment*
temporarily.
user=> (binding [*increment* 3]
(doall (map incr [1 2 3])))
(4 5 6)
Yeah! It works!
As I said in the introductory paragraph: Clojure provides several facilities
to handle concurrency. Besides Vars there is for example pmap
. pmap
is
basically a drop-in replacement for map
which distributes the computation
of the output seq on several threads. So each map
can be easily turned to
parallel construct by simply adding a single character. (Whether this makes
sense in every case is a different question!)
So, let's try pmap
for fun and profit!
user=> (binding [*increment* 3]
(doall (pmap incr [1 2 3])))
(2 3 4)
Huh? Argh! We got caught by the thread-localness of Vars. The changed value
of *increment*
established with binding
is not carried over to the new
thread, where the actual computation is done. There the root binding is in
effect and we get the original value of *increment*
.
To fix this situation we could take a hands-down approach and simply re-establish the binding in the target thread.
user=> (binding [*increment* 3]
(doall (pmap (let [step *increment*]
(fn [x] (binding [*increment* step] (incr x))))
[1 2 3])))
(4 5 6)
This looks a bit silly, but imagine the binding
being somewhere else
in the code in a real-world project. So what do we do? We capture the
thread-local binding in a local and pass a closure to pmap
. This closure
sets up the binding on the new thread before invocing incr
.
This works but is quite a bit of tedious boilerplate. But luckily Clojure provides a helper utility for this scenario!
user=> (binding [*increment* 3]
(doall (pmap (bound-fn [x] (incr x)) [1 2 3])))
(4 5 6)
bound-fn
defines a function – similar to fn
. However with the difference
that it restores any thread-local bindings before executing the body of the
function. So it is basically an extended approach to the above let
solution.
In our case we can simplify things even more: we already have a function. We
should be able to turn this function into a different one with the desired
properties – similar to how memoize
works. And in fact we can do that!
user=> (binding [*increment* 3]
(doall (pmap (bound-fn* incr) [1 2 3])))
(4 5 6)
A combination of Vars and binding
can be a way to modify the environment
of the code, eg. by rebinding *out*
. However one has to take care when
crossing thread boundaries. The bound-fn
and bound-fn*
utilities help
in such a situation.
binding
binds in parallel, while let
binds sequentially.doall
s. Why this is
necessary is explained in a previous post.Published by Meikel Brandmeyer on .
I'm a long-time Clojure user and the developer of several open source projects mostly involving Clojure. I try to actively contribute to the Clojure community.
My most active projects are at the moment VimClojure, Clojuresque and ClojureCheck.
Copyright © 2009-2014 All Right Reserved. Meikel Brandmeyer