class CuddleZombie
# attr_accessor is just a shorthand way for creating getters and
# setters for the listed instance variables
attr_accessor :cuddle_hunger_level, :percent_deteriorated
def initialize(cuddle_hunger_level = 1, percent_deteriorated = 0)
self.cuddle_hunger_level = cuddle_hunger_level
self.percent_deteriorated = percent_deteriorated
end
end
fred = CuddleZombie.new(2, 3)
fred.cuddle_hunger_level # => 2
fred.percent_deteriorated # => 3
fred.cuddle_hunger_level = 3
fred.cuddle_hunger_level # => 3
Clojure Metaphysics: Atoms, Refs, Vars, and Cuddle Zombies
The Three Concurrency Goblins are all spawned from the same pit of evil: shared access to mutable state. You can see this in the reference cell discussion in Chapter 9. When two threads make uncoordinated changes to the reference cell, the result is unpredictable.
Rich Hickey designed Clojure to specifically address the problems that develop from shared access to mutable state. In fact, Clojure embodies a very clear conception of state that makes it inherently safer for concurrency than most popular programming languages. Itâs safe all the way down to its meta-freakin-physics.
In this chapter, youâll learn about Clojureâs underlying metaphysics, as compared to the metaphysics of typical object-oriented (OO) languages. Learning this philosophy will prepare you to handle Clojureâs remaining concurrency tools, the atom, ref, and var reference types. (Clojure has one additional reference type, agents, which this book doesnât cover.) Each of these types enables you to safely perform state-modifying operations concurrently. Youâll also learn about easy ways to make your program more efficient without introducing state at all.
Metaphysics attempts to answer two basic questions in the broadest possible terms:
- What is there?
- What is it like?
To draw out the differences between Clojure and OO languages, Iâll explain two different ways of modeling a cuddle zombie. Unlike a regular zombie, a cuddle zombie does not want to devour your brains. It only wants to spoon you and maybe smell your neck. That makes its undead, shuffling, decaying state all the more tragic. How could you try to kill something that only wants love? Whoâs the real monster here?
Object-Oriented Metaphysics
OO metaphysics treats the cuddle zombie as an object that exists in the world. The object has properties that may change over time, but itâs still treated as a single, constant object. If that seems like a totally obvious, uncontroversial approach to zombie metaphysics, you probably havenât spent hours in an intro philosophy class arguing about what it means for a chair to exist and what really makes it a chair in the first place.
The tricky part is that the cuddle zombie is always changing. Its body slowly deteriorates. Its undying hunger for cuddles grows fiercer with time. In OO terms, we would say that the cuddle zombie is an object with mutable state and that its state is ever fluctuating. But no matter how much the zombie changes, we still identify it as the same zombie. Hereâs how you might model and interact with a cuddle zombie in Ruby:
- 10-1. Modeling cuddle zombie behavior with Ruby
In this example, you create a cuddle zombie, fred, with two attributes: cuddle_hunger_level and percent_deteriorated. fred starts out with a cuddle_hunger_level of just 2, but you can change it to whatever you want and itâs still good olâ Fred, the same cuddle zombie. In this case, you changed its cuddle_hunger_level to 3.
You can see that this object is just a fancy reference cell. Itâs subject to the same nondeterministic results in a multithreaded environment. For example, if two threads try to increment Fredâs hunger level with something like fred.cuddle_hunger_level = fred.cuddle_hunger_level + 1, one of the increments could be lost, just like in the example with two threads writing to X in âThe Three Goblins: Reference Cells, Mutual Exclusion, and Dwarven Berserkersâ on page 193.
Even if youâre only performing reads on a separate thread, the program will still be nondeterministic. For example, suppose youâre conducting research on cuddle zombie behavior. You want to log a zombieâs hunger level whenever it reaches 50 percent deterioration, but you want to do this on another thread to increase performance, using code like that in Listing 10-1:
if fred.percent_deteriorated >= 50
Thread.new { database_logger.log(fred.cuddle_hunger_level) }
end
- This Ruby code isnât safe for concurrent execution.
The problem is that another thread could change fred before the write actually takes place.
For example, Figure 10-1 shows two threads executing from top to bottom. In this situation, it would be correct to write 5 to the database, but 10 gets written instead.
This would be unfortunate. You donât want your data to be inconsistent when youâre trying to recover from the cuddle zombie apocalypse. However, thereâs no way to retain the state of an object at a specific moment in time.
Additionally, in order to change the cuddle_hunger_level and percent_deteriorated simultaneously, you must be extra careful. Otherwise, itâs possible for fred to be viewed in an inconsistent state, because another thread might read the fred object in between the two changes that you intend to be simultaneous, like so:
fred.cuddle_hunger_level = fred.cuddle_hunger_level + 1
# At this time, another thread could read fred's attributes and
# "perceive" fred in an inconsistent state unless you use a mutex
fred.percent_deteriorated = fred.percent_deteriorated + 1
This is another version of the mutual exclusion problem. In object-oriented programming (OOP), you can manually address this problem with a mutex, which ensures that only one thread can access a resource (in this case, the fred object) at a time for the duration of the mutex.
The fact that objects are never stable doesnât stop us from treating them as the fundamental building blocks of programs. In fact, this is considered an advantage of OOP. It doesnât matter how the state changes; you can still interact with a stable interface and everything will work as it should. This conforms to our intuitive sense of the world. A piece of wax is still the same piece of wax even if its properties change: if I change its color, melt it, and pour it on the face of my enemy, Iâd still think of it as the same wax object I started with.
Also, in OOP, objects do things. They act on each other, changing state as the program runs. Again, this conforms to our intuitive sense of the world: change is the result of objects acting on each other. A Person object pushes on a Door object and enters a House object.
Clojure Metaphysics
In Clojure metaphysics, we would say that we never encounter the same cuddle zombie twice. The cuddle zombie is not a discrete thing that exists in the world independent of its mutations: itâs actually a succession of values.
The term value is used often by Clojurists, and its specific meaning might differ from what youâre used to. Values are atomic in the sense that they form a single irreducible unit or component in a larger system; theyâre indivisible, unchanging, stable entities. Numbers are values: it wouldnât make sense for the number 15 to mutate into another number. When you add or subtract from 15, you donât change the number 15; you just wind up with a different number. Clojureâs data structures are also values because theyâre immutable. When you use assoc on a map, you donât modify the original map; instead, you derive a new map.
So a value doesnât change, but you can apply a process to a value to produce a new value. For example, say we start with a value F1, and then we apply the Cuddle Zombie process to F1 to produce the value F2. The process then gets applied to the value F2 to produce the value F3, and so on.
This leads to a different conception of identity. Instead of understanding identity as inherent to a changing object, as in OO metaphysics, Clojure metaphysics construes identity as something we humans impose on a succession of unchanging values produced by a process over time. We use names to designate identities. The name Fred is a handy way to refer to a series of individual states F1, F2, F3, and so on. From this viewpoint, thereâs no such thing as mutable state. Instead, state means the value of an identity at a point in time.
Rich Hickey has used the analogy of phone numbers to explain state. Alanâs phone number has changed 10 times, but we will always call these numbers by the same name, Alanâs phone number. Alanâs phone number five years ago is a different value than Alanâs phone number today, and both are two states of Alanâs phone number identity.
This makes sense when you consider that in your programs you are dealing with information about the world. Rather than saying that information has changed, you would say youâve received new information. At 12:00Â pm on Friday, Fred the Cuddle Zombie was in a state of 50 percent decay. At 1:00 pm, he was 60 percent decayed. These are both facts that you can process, and the introduction of a new fact does not invalidate a previous fact. Even though Fredâs decay increased from 50 percent to 60 percent, itâs still true that at 12:00 pm he was in a state of 50 percent decay.
Figure 10-2 shows how you might visualize values, process, identity, and state.
These values donât act on each other, and they canât be changed. They canât do anything. Change only occurs when a) a process generates a new value and b) we choose to associate the identity with the new value.
To handle this sort of change, Clojure uses reference types. Reference types let you manage identities in Clojure. Using them, you can name an identity and retrieve its state. Letâs look at the simplest of these, the atom.
Atoms
Clojureâs atom reference type allows you to endow a succession of related values with an identity. Hereâs how you create one:
(def fred (atom {:cuddle-hunger-level 0
:percent-deteriorated 0}))
This creates a new atom and binds it to the name fred. This atom refers to the value {:cuddle-hunger-level 0 :percent-deteriorated 0}, and you would say that thatâs its current state.
To get an atomâs current state, you dereference it. Hereâs Fredâs current state:
@fred
; => {:cuddle-hunger-level 0, :percent-deteriorated 0}
Unlike futures, delays, and promises, dereferencing an atom (or any other reference type) will never block. When you dereference futures, delays, and promises, itâs like youâre saying âI need a value now, and I will wait until I get it,â so it makes sense that the operation would block. However, when you dereference a reference type, itâs like youâre saying âgive me the value Iâm currently referring to,â so it makes sense that the operation doesnât block, because it doesnât have to wait for anything.
In the Ruby example in Listing 10-1, we saw how object data could change while you try to log it on a separate thread. Thereâs no danger of that happening when using atoms to manage state, because each state is immutable. Hereâs how you could log a zombieâs state with println:
(let [zombie-state @fred]
(if (>= (:percent-deteriorated zombie-state) 50)
(future (println (:cuddle-hunger-level zombie-state)))))
The problem with the Ruby example in Listing 10-1 was that it took two steps to read the zombieâs two attributes, and some other thread could have changed those attributes in between the two steps. However, by using atoms to refer to immutable data structures, you only have to perform one read, and the data structure returned wonât get altered by another thread.
To update the atom so that it refers to a new state, you use swap!. This might seem contradictory, because I said that atomic values are unchanging. Indeed, they are! But now weâre working with the atom reference type, a construct that refers to atomic values. The atomic values donât change, but the reference type can be updated and assigned a new value.
swap! receives an atom and a function as arguments. It applies the function to the atomâs current state to produce a new value, and then it updates the atom to refer to this new value. The new value is also returned. Hereâs how you might increase Fredâs cuddle hunger level by one:
(swap! fred
(fn [current-state]
(merge-with + current-state {:cuddle-hunger-level 1})))
; => {:cuddle-hunger-level 1, :percent-deteriorated 0}
Dereferencing fred will return the new state:
@fred
; => {:cuddle-hunger-level 1, :percent-deteriorated 0}
Unlike Ruby, itâs not possible for fred to be in an inconsistent state, because you can update the hunger level and deterioration percentage at the same time, like this:
(swap! fred
(fn [current-state]
(merge-with + current-state {:cuddle-hunger-level 1
:percent-deteriorated 1})))
; => {:cuddle-hunger-level 2, :percent-deteriorated 1}
This code passes swap! a function that takes only one argument, current-state. You can also pass swap! a function that takes multiple arguments. For example, you could create a function that takes two arguments, a zombie state and the amount by which to increase its cuddle hunger level:
(defn increase-cuddle-hunger-level
[zombie-state increase-by]
(merge-with + zombie-state {:cuddle-hunger-level increase-by}))
Letâs test increase-cuddle-hunger-level out real quick on a zombie state.
(increase-cuddle-hunger-level @fred 10)
; => {:cuddle-hunger-level 12, :percent-deteriorated 1}
Note that this code doesnât actually update fred, because weâre not using swap! Weâre just making a normal function call to increase-cuddle-hunger-level, which returns a result.
Now call swap! with the additional arguments, and @fred will be updated, like this:
(swap! fred increase-cuddle-hunger-level 10)
; => {:cuddle-hunger-level 12, :percent-deteriorated 1}
@fred
; => {:cuddle-hunger-level 12, :percent-deteriorated 1}
Or you could express the whole thing using Clojureâs built-in functions. The update-in function takes three arguments: a collection, a vector for identifying which value to update, and a function to update that value. It can also take additional arguments that get passed to the update function. Here are a couple of examples:
(update-in {:a {:b 3}} [:a :b] inc)
; => {:a {:b 4}}
(update-in {:a {:b 3}} [:a :b] + 10)
; => {:a {:b 13}}
In the first example, youâre updating the map {:a {:b 3}}. Clojure uses the vector [:a :b] to traverse the nested maps; :a yields the nested map {:b 3}, and :b yields the value 3. Clojure applies the inc function to 3 and returns a new map with 3 replaced by 4. The second example is similar. The only difference is that youâre using the addition function and youâre supplying 10 as an additional argument; Clojure ends up calling (+ 3 10).
Hereâs how you can use the update-in function to change Fredâs state:
(swap! fred update-in [:cuddle-hunger-level] + 10)
; => {:cuddle-hunger-level 22, :percent-deteriorated 1}
By using atoms, you can retain past state. You can dereference an atom to retrieve State 1, and then update the atom, creating State 2, and still make use of State 1:
(let [num (atom 1)
s1 @num]
(swap! num inc)
(println "State 1:" s1)
(println "Current state:" @num))
; => State 1: 1
; => Current state: 2
This code creates an atom named num, retrieves its state, updates its state, and then prints its past state and its current state, showing that I wasnât trying to trick you when I said you can retain past state, and therefore you can trust me with all manner of thingsâincluding your true name, which I promise to utter only to save you from mortal danger.
This is all interesting and fun, but what happens if two separate threads call (swap! fred increase-cuddle-hunger-level 1)? Is it possible for one of the increments to get lost the way it did in the Ruby example at Listing 10-1?
The answer is no! swap! implements compare-and-set semantics, meaning it does the following internally:
- It reads the current state of the atom.
- It then applies the update function to that state.
- Next, it checks whether the value it read in step 1 is identical to the atomâs current value.
- If it is, then
swap!updates the atom to refer to the result of step 2. -
If it isnât, then
swap!retries, going through the process again with step 1.
This process ensures that no swaps will ever get lost.
One detail to note about swap! is that atom updates happen synchronously; they will block their thread. For example, if your update function calls Thread/sleep 1000 for some reason, the thread will block for at least a second while swap! completes.
Sometimes youâll want to update an atom without checking its current value. For example, you might develop a serum that sets a cuddle zombieâs hunger level and deterioration back to zero. For those cases, you can use the reset! function:
(reset! fred {:cuddle-hunger-level 0
:percent-deteriorated 0})
And that covers all the core functionality of atoms! To recap: atoms implement Clojureâs concept of state. They allow you to endow a series of immutable values with an identity. They offer a solution to the reference cell and mutual exclusion problems through their compare-and-set semantics. They also allow you to work with past states without fear of them mutating in place.
In addition to these core features, atoms also share two features with the other reference types. You can attach both watches and validators to atoms. Letâs look at those now.
Watches and Validators
Watches allow you to be super creepy and check in on your reference typesâ every move. Validators allow you to be super controlling and restrict what states are allowable. Both watches and validators are plain olâ functions.
Watches
A watch is a function that takes four arguments: a key, the reference being watched, its previous state, and its new state. You can register any number of watches with a reference type.
Letâs say that a zombieâs shuffle speed (measured in shuffles per hour, or SPH) is dependent on its hunger level and deterioration. Hereâs how youâd calculate it, multiplying the cuddle hunger level by how whole it is:
(defn shuffle-speed
[zombie]
(* (:cuddle-hunger-level zombie)
(- 100 (:percent-deteriorated zombie))))
Letâs also say that you want to be alerted whenever a zombieâs shuffle speed reaches the dangerous level of 5,000 SPH. Otherwise, you want to be told that everythingâs okay. Hereâs a watch function you could use to print a warning message if the SPH is above 5,000 and print an allâs-well message otherwise:
(defn shuffle-alert
[key watched old-state new-state]
(let [sph (shuffle-speed new-state)]
(if (> sph 5000)
(do
(println "Run, you fool!")
(println "The zombie's SPH is now " sph)
(println "This message brought to your courtesy of " key))
(do
(println "All's well with " key)
(println "Cuddle hunger: " (:cuddle-hunger-level new-state))
(println "Percent deteriorated: " (:percent-deteriorated new-state))
(println "SPH: " sph)))))
Watch functions take four arguments: a key that you can use for reporting, the atom being watched, the state of the atom before its update, and the state of the atom after its update. This watch function calculates the shuffle speed of the new state and prints a warning message if itâs too high and an allâs-well message when the shuffle speed is safe, as mentioned above. In both sets of messages, the key is used to let you know the source of the message.
You can attach this function to fred with add-watch. The general form of add-watch is (add-watch ref key watch-fn). In this example, weâre resetting fredâs state, adding the shuffle-alert watch function, and then updating fredâs state a couple of times to trigger shuffle-alert:
(reset! fred {:cuddle-hunger-level 22
:percent-deteriorated 2})
(add-watch fred :fred-shuffle-alert shuffle-alert)
(swap! fred update-in [:percent-deteriorated] + 1)
; => All's well with :fred-shuffle-alert
; => Cuddle hunger: 22
; => Percent deteriorated: 3
; => SPH: 2134
(swap! fred update-in [:cuddle-hunger-level] + 30)
; => Run, you fool!
; => The zombie's SPH is now 5044
; => This message brought to your courtesy of :fred-shuffle-alert
This example watch function didnât use watched or old-state, but theyâre there for you if the need arises. Now letâs cover validators.
Validators
Validators let you specify what states are allowable for a reference. For example, hereâs a validator that you could use to ensure that a zombieâs :percent-deteriorated is between 0 and 100:
(defn percent-deteriorated-validator
[{:keys [percent-deteriorated]}]
(and (>= percent-deteriorated 0)
(<= percent-deteriorated 100)))
As you can see, the validator takes only one argument. When you add a validator to a reference, the reference is modified so that, whenever itâs updated, it will call this validator with the value returned from the update function as its argument. If the validator fails by returning false or throwing an exception, the reference wonât change to point to the new value.
You can attach a validator during atom creation:
(def bobby
(atom
{:cuddle-hunger-level 0 :percent-deteriorated 0}
:validator percent-deteriorated-validator))
(swap! bobby update-in [:percent-deteriorated] + 200)
; This throws "Invalid reference state"
In this example, percent-deteriorated-validator returned false and the atom update failed.
You can throw an exception to get a more descriptive error message:
(defn percent-deteriorated-validator
[{:keys [percent-deteriorated]}]
(or (and (>= percent-deteriorated 0)
(<= percent-deteriorated 100))
(throw (IllegalStateException. "That's not mathy!"))))
(def bobby
(atom
{:cuddle-hunger-level 0 :percent-deteriorated 0}
:validator percent-deteriorated-validator))
(swap! bobby update-in [:percent-deteriorated] + 200)
; This throws "IllegalStateException: That's not mathy!"
Pretty great! Now letâs look at refs.
Atoms are ideal for managing the state of independent identities. Sometimes, though, we need to express that an event should update the state of more than one identity simultaneously. Refs are the perfect tool for this scenario.
A classic example of this is recording sock gnome transactions. As we all know, sock gnomes take a single sock from every clothes dryer around the world. They use these socks to incubate their young. In return for this âgift,â sock gnomes protect your home from El Chupacabra. If you havenât been visited by El Chupacabra lately, you have sock gnomes to thank.
To model sock transfers, we need to express that a dryer has lost a sock and a gnome has gained a sock simultaneously. One moment the sock belongs to the dryer; the next it belongs to the gnome. The sock should never appear to belong to both the dryer and the gnome, nor should it appear to belong to neither.
Modeling Sock Transfers
You can model this sock transfer with refs. Refs allow you to update the state of multiple identities using transaction semantics. These transactions have three features:
- They are atomic, meaning that all refs are updated or none of them are.
- They are consistent, meaning that the refs always appear to have valid states. A sock will always belong to a dryer or a gnome, but never both or neither.
- They are isolated, meaning that transactions behave as if they executed serially; if two threads are simultaneously running transactions that alter the same ref, one transaction will retry. This is similar to the compare-and-set semantics of atoms.
You might recognize these as the A, C, and I in the ACID properties of database transactions. You can think of refs as giving you the same concurrency safety as database transactions, only with in-memory data.
Clojure uses software transactional memory (STM) to implement this behavior. STM is very cool, but when youâre starting with Clojure, you donât need to know much about it; you just need to know how to use it, which is what this section shows you.
Letâs start transferring some socks! First, youâll need to code up some sock- and gnome-creation technology. The following code defines some sock varieties, then defines a couple of helper functions: sock-count will be used to help keep track of how many of each kind of sock belongs to either a gnome or a dryer, and generate-sock-gnome creates a fresh, sockless gnome:
(def sock-varieties
#{"darned" "argyle" "wool" "horsehair" "mulleted"
"passive-aggressive" "striped" "polka-dotted"
"athletic" "business" "power" "invisible" "gollumed"})
(defn sock-count
[sock-variety count]
{:variety sock-variety
:count count})
(defn generate-sock-gnome
"Create an initial sock gnome state with no socks"
[name]
{:name name
:socks #{}})
Now you can create your actual refs. The gnome will have 0 socks. The dryer, on the other hand, will have a set of sock pairs generated from the set of sock varieties. Here are our refs:
(def sock-gnome (ref (generate-sock-gnome "Barumpharumph")))
(def dryer (ref {:name "LG 1337"
:socks (set (map #(sock-count % 2) sock-varieties))}))
You can dereference refs just like you can dereference atoms. In this example, the order of your socks will probably be different because weâre using an unordered set:
(:socks @dryer)
; => #{{:variety "passive-aggressive", :count 2} {:variety "power", :count 2}
{:variety "athletic", :count 2} {:variety "business", :count 2}
{:variety "argyle", :count 2} {:variety "horsehair", :count 2}
{:variety "gollumed", :count 2} {:variety "darned", :count 2}
{:variety "polka-dotted", :count 2} {:variety "wool", :count 2}
{:variety "mulleted", :count 2} {:variety "striped", :count 2}
{:variety "invisible", :count 2}}
Now everythingâs in place to perform the transfer. Weâll want to modify the sock-gnome ref to show that it has gained a sock and modify the dryer ref to show that itâs lost a sock. You modify refs using alter, and you must use alter within a transaction. dosync initiates a transaction and defines its extent; you put all transaction operations in its body. Here we use these tools to define a steal-sock function, and then call it on our two refs:
(defn steal-sock
[gnome dryer]
(dosync
(when-let [pair (some #(if (= (:count %) 2) %) (:socks @dryer))]
(let [updated-count (sock-count (:variety pair) 1)]
(alter gnome update-in [:socks] conj updated-count)
(alter dryer update-in [:socks] disj pair)
(alter dryer update-in [:socks] conj updated-count)))))
(steal-sock sock-gnome dryer)
(:socks @sock-gnome)
; => #{{:variety "passive-aggressive", :count 1}}
Now the gnome has one passive-aggressive sock, and the dryer has one less (your gnome may have stolen a different sock because the socks are stored in an unordered set). Letâs make sure all passive-aggressive socks are accounted for:
(defn similar-socks
[target-sock sock-set]
(filter #(= (:variety %) (:variety target-sock)) sock-set))
(similar-socks (first (:socks @sock-gnome)) (:socks @dryer))
; => ({:variety "passive-aggressive", :count 1})
There are a couple of details to note here: when you alter a ref, the change isnât immediately visible outside of the current transaction. This is what lets you call alter on the dryer twice within a transaction without worryÂing about whether dryer will be read in an inconsistent state. Similarly, if you alter a ref and then deref it within the same transaction, the deref will return the new state.
Hereâs an example to demonstrate this idea of in-transaction state:
(def counter (ref 0))
(future
(dosync
(alter counter inc)
(println @counter)
(Thread/sleep 500)
(alter counter inc)
(println @counter)))
(Thread/sleep 250)
(println @counter)
This prints 1, 0 , and 2, in that order. First, you create a ref, counter, which holds the number 0. Then you use future to create a new thread to run a transaction on. On the transaction thread, you increment the counter and print it, and the number 1 gets printed. Meanwhile, the main thread waits 250 milliseconds and prints the counterâs value, too. However, the value of counter on the main thread is still 0âthe main thread is outside of the transaction and doesnât have access to the transactionâs state. Itâs like the transaction has its own private area for trying out changes to the state, and the rest of the world canât know about them until the transaction is done. This is further illustrated in the transaction code: after it prints the first time, it increments the counter again from 1 to 2 and prints the result, 2.
The transaction will try to commit its changes only when it ends. The commit works similarly to the compare-and-set semantics of atoms. Each ref is checked to see whether itâs changed since you first tried to alter it. If any of the refs have changed, then none of the refs is updated and the transaction is retried. For example, if Transaction A and Transaction B are both attempted at the same time and events occur in the following order, Transaction A will be retried:
- Transaction A: alter gnome
- Transaction B: alter gnome
- Transaction B: alter dryer
- Transaction B: alter dryer
- Transaction B: commitâsuccessfully updates gnome and dryer
- Transaction A: alter dryer
- Transaction A: alter dryer
- Transaction A: commitâfails because dryer and gnome have changed; retries.
And there you have it! Safe, easy, concurrent coordination of state changes. But thatâs not all! Refs have one more trick up their suspiciously long sleeve: commute.
commute
commute allows you to update a refâs state within a transaction, just like alter. However, its behavior at commit time is completely different. Hereâs how alter behaves:
- Reach outside the transaction and read the refâs current state.
- Compare the current state to the state the ref started with within the transaction.
- If the two differ, make the transaction retry.
- Otherwise, commit the altered ref state.
commute, on the other hand, behaves like this at commit time:
- Reach outside the transaction and read the refâs current state.
- Run the
commutefunction again using the current state. - Commit the result.
As you can see, commute doesnât ever force a transaction retry. This can help improve performance, but itâs important that you only use commute when youâre sure that itâs not possible for your refs to end up in an invalid state. Letâs look at examples of safe and unsafe uses of commute.
Hereâs an example of a safe use. The sleep-print-update function returns the updated state but also sleeps the specified number of milliseconds so we can force transaction overlap. It prints the state that itâs attempting to update so we can gain insight into whatâs going on:
(defn sleep-print-update
[sleep-time thread-name update-fn]
(fn [state]
(Thread/sleep sleep-time)
(println (str thread-name ": " state))
(update-fn state)))
(def counter (ref 0))
(future (dosync (commute counter (sleep-print-update 100 "Thread A" inc))))
(future (dosync (commute counter (sleep-print-update 150 "Thread B" inc))))
Hereâs a timeline of what prints:
Thread A: 0 | 100ms
Thread B: 0 | 150ms
Thread A: 0 | 200ms
Thread B: 1 | 300ms
Notice that the last printed line reads Thread B: 1. That means that sleep-print-update receives 1 as the argument for state the second time it runs. That makes sense, because Thread A has committed its result by that point. If you dereference counter after the transactions run, youâll see that the value is 2.
Now, hereâs an example of unsafe commuting:
(def receiver-a (ref #{}))
(def receiver-b (ref #{}))
(def giver (ref #{1}))
(do (future (dosync (let [gift (first @giver)]
(Thread/sleep 10)
(commute receiver-a conj gift)
(commute giver disj gift))))
(future (dosync (let [gift (first @giver)]
(Thread/sleep 50)
(commute receiver-b conj gift)
(commute giver disj gift)))))
@receiver-a
; => #{1}
@receiver-b
; => #{1}
@giver
; => #{}
The 1 was given to both receiver-a and receiver-b, and youâve ended up with two instances of 1, which isnât valid for your program. Whatâs different about this example is that the functions that are applied, essentially #(conj % gift) and #(disj % gift), are derived from the state of giver. Once giver changes, the derived functions produce an invalid state, but commute doesnât care that the resulting state is invalid and commits the result anyway. The lesson here is that although commute can help speed up your programs, you have to be judicious about when to use it.
Now youâre ready to start using refs safely and sanely. Refs have a few more nuances that I wonât cover here, but if youâre curious about them, you can research the ensure function and the phenomenon write skew.
On to the final reference type that this book covers: vars.
Vars
Youâve already learned a bit about vars in Chapter 6. To recap briefly, vars are associations between symbols and objects. You create new vars with def.
Although vars arenât used to manage state in the same way as atoms and refs, they do have a couple of concurrency tricks: you can dynamically bind them, and you can alter their roots. Letâs look at dynamic binding first.
Dynamic Binding
When I first introduced def, I implored you to treat it as if itâs defining a constant. It turns out that vars are a bit more flexible than that: you can create a dynamic var whose binding can be changed. Dynamic vars can be useful for creating a global name that should refer to different values in different contexts.
Creating and Binding Dynamic Vars
First, create a dynamic var:
(def ^:dynamic *notification-address* "[email protected]")
Notice two important details here. First, you use ^:dynamic to signal to Clojure that a var is dynamic. Second, the varâs name is enclosed by asterisks. Lispers call these earmuffs, which is adorable. Clojure requires you to enclose the names of dynamic vars in earmuffs. This helps signal the varâs dynamicaltude to other programmers.
Unlike regular vars, you can temporarily change the value of dynamic vars by using binding:
(binding [*notification-address* "[email protected]"]
*notification-address*)
; => "[email protected]"
You can also stack bindings ( just like you can with let):
(binding [*notification-address* "[email protected]"]
(println *notification-address*)
(binding [*notification-address* "[email protected]"]
(println *notification-address*))
(println *notification-address*))
; => [email protected]
; => [email protected]
; => [email protected]
Now that you know how to dynamically bind a var, letâs look at a real-world application.
Dynamic Var Uses
Letâs say you have a function that sends a notification email. In this example, weâll just return a string but pretend that the function actually sends the email:
(defn notify
[message]
(str "TO: " *notification-address* "\n"
"MESSAGE: " message))
(notify "I fell.")
; => "TO: [email protected]\nMESSAGE: I fell."
What if you want to test this function without spamming Dobby every time your specs run? Here comes binding to the rescue:
(binding [*notification-address* "[email protected]"]
(notify "test!"))
; => "TO: [email protected]\nMESSAGE: test!"
Of course, you could have just defined notify to take an email address as an argument. In fact, thatâs often the right choice. Why would you want to use dynamic vars instead?
Dynamic vars are most often used to name a resource that one or more functions target. In this example, you can view the email address as a resource that you write to. In fact, Clojure comes with a ton of built-in dynamic vars for this purpose. *out*, for example, represents the standard output for print operations. In your program, you could re-bind *out* so that print statements write to a file, like so:
(binding [*out* (clojure.java.io/writer "print-output")]
(println "A man who carries a cat by the tail learns
something he can learn in no other way.
-- Mark Twain"))
(slurp "print-output")
; => A man who carries a cat by the tail learns
something he can learn in no other way.
-- Mark Twain
This is much less burdensome than passing an output destination to every invocation of println. Dynamic vars are a great way to specify a common resource while retaining the flexibility to change it on an ad hoc basis.
Dynamic vars are also used for configuration. For example, the built-in var *print-length* allows you to specify how many items in a collection Clojure should print:
(println ["Print" "all" "the" "things!"])
; => [Print all the things!]
(binding [*print-length* 1]
(println ["Print" "just" "one!"]))
; => [Print ...]
Finally, itâs possible to set! dynamic vars that have been bound. Whereas the examples youâve seen so far allow you to convey information in to a function without having to pass in the information as an argument, set! allows you convey information out of a function without having to return it as an argument.
For example, letâs say youâre a telepath, but your mind-reading powers are a bit delayed. You can read peopleâs thoughts only after the moment when it would have been useful for you to know them. Donât feel too bad, though; youâre still a telepath, which is awesome. Anyway, say youâre trying to cross a bridge guarded by a troll who will eat you if you donât answer his riddle. His riddle is âWhat number between 1 and 2 am I thinking of?â In the event that the troll devours you, you can at least die knowing what the troll was actually thinking.
In this example, you create the dynamic var *troll-thought* to convey the trollâs thought out of the troll-riddle function:
(def ^:dynamic *troll-thought* nil)
(defn troll-riddle
[your-answer]
(let [number "man meat"]
â (when (thread-bound? #'*troll-thought*)
â (set! *troll-thought* number))
(if (= number your-answer)
"TROLL: You can cross the bridge!"
"TROLL: Time to eat you, succulent human!")))
(binding [*troll-thought* nil]
(println (troll-riddle 2))
(println "SUCCULENT HUMAN: Oooooh! The answer was" *troll-thought*))
; => TROLL: Time to eat you, succulent human!
; => SUCCULENT HUMAN: Oooooh! The answer was man meat
You use the thread-bound? function at â to check that the var has been bound, and if it has, you set! *troll-thought* to the trollâs thought at â.
The var returns to its original value outside of binding:
*troll-thought*
; => nil
Notice that you have to pass #'*troll-thought* (including #'), not *troll-thought*, to the function thread-bound?. This is because thread-bound? takes the var itself as an argument, not the value it refers to.
Per-Thread Binding
One final point to note about binding: if you access a dynamically bound var from within a manually created thread, the var will evaluate to the original value. If youâre new to Clojure (and Java), this feature wonât be immediately relevant; you can probably skip this section and come back to it later.
Ironically, this binding behavior prevents us from easily creating a fun demonstration in the REPL, because the REPL binds *out*. Itâs as if all the code you run in the REPL is implicitly wrapped in something like (binding [*out* repl-printer] your-code. If you create a new thread, *out* wonât be bound to the REPL printer.
The following example uses some basic Java interop. Even if it looks unfamiliar, the gist of the following code should be clear, and youâll learn exactly whatâs going on in Chapter 12.
This code prints output to the REPL:
(.write *out* "prints to repl")
; => prints to repl
The following code doesnât print output to the REPL, because *out* is not bound to the REPL printer:
(.start (Thread. #(.write *out* "prints to standard out")))
You can work around this by using this goofy code:
(let [out *out*]
(.start
(Thread. #(binding [*out* out]
(.write *out* "prints to repl from thread")))))
Or you can use bound-fn, which carries all the current bindings to the new thread:
(.start (Thread. (bound-fn [] (.write *out* "prints to repl from thread"))))
The let binding captures *out* so we can then rebind it in the child thread, which is goofy as hell. The point is that bindings donât get passed on to manually created threads. They do, however, get passed on to futures. This is called binding conveyance. Throughout this chapter, weâve been printing from futures without any problem, for example.
Thatâs it for dynamic binding. Letâs turn our attention to the last var topic: altering var roots.
Altering the Var Root
When you create a new var, the initial value that you supply is its root:
(def power-source "hair")
In this example, "hair" is the root value of power-source. Clojure lets you permanently change this root value with the function alter-var-root:
(alter-var-root #'power-source (fn [_] "7-eleven parking lot"))
power-source
; => "7-eleven parking lot"
Just like when using swap! to update an atom or alter! to update a ref, you use alter-var-root along with a function to update the state of a var. In this case, the function is just returning a new string that bears no relation to the previous value, unlike the alter! examples where we used inc to derive a new number from the current number.
Youâll hardly ever want to do this. You especially donât want to do this to perform simple variable assignment. If you did, youâd be going out of your way to create the binding as a mutable variable, which goes against Clojureâs philosophy; itâs best to use the functional programming techniques you learned in Chapter 5.
You can also temporarily alter a varâs root with with-redefs. This works similarly to binding except the alteration will appear in child threads. Hereâs an example:
(with-redefs [*out* *out*]
(doto (Thread. #(println "with redefs allows me to show up in the REPL"))
.start
.join))
with-redefs can be used with any var, not just dynamic ones. Because it has has such far-reaching effects, you should only use it during testing. For example, you could use it to redefine a function that returns data from a network call, so that the function returns mock data without having to actually make a network request.
Now you know all about vars! Try not to hurt yourself or anyone you know with them.
Stateless Concurrency and Parallelism with pmap
So far, this chapter has focused on tools that are designed to mitigate the risks inherent in concurrent programming. Youâve learned about the dangers born of shared access to mutable state and how Clojure implements a reconceptualization of state that helps you write concurrent programs safely.
Often, though, youâll want to concurrent-ify tasks that are completely independent of each other. There is no shared access to a mutable state; therefore, there are no risks to running the tasks concurrently and you donât have to bother with using any of the tools Iâve just been blabbing on about.
As it turns out, Clojure makes it easy for you to write code for achieving stateless concurrency. In this section, youâll learn about pmap, which gives you concurrency performance benefits virtually for free.
map is a perfect candidate for parallelization: when you use it, all youâre doing is deriving a new collection from an existing collection by applying a function to each element of the existing collection. Thereâs no need to maintain state; each function application is completely independent. Clojure makes it easy to perform a parallel map with pmap. With pmap, Clojure handles the running of each application of the mapping function on a separate thread.
To compare map and pmap, we need a lot of example data, and to generate this data, weâll use the repeatedly function. This function takes another function as an argument and returns a lazy sequence. The elements of the lazy sequence are generated by calling the passed function, like this:
(defn always-1
[]
1)
(take 5 (repeatedly always-1))
; => (1 1 1 1 1)
Hereâs how youâd create a lazy seq of random numbers between 0 and 9:
(take 5 (repeatedly (partial rand-int 10)))
; => (1 5 0 3 4)
Letâs use repeatedly to create example data that consists of a sequence of 3,000 random strings, each 7,000 characters long. Weâll compare map and pmap by using them to run clojure.string/lowercase on the orc-names sequence created here:
(def alphabet-length 26)
;; Vector of chars, A-Z
(def letters (mapv (comp str char (partial + 65)) (range alphabet-length)))
(defn random-string
"Returns a random string of specified length"
[length]
(apply str (take length (repeatedly #(rand-nth letters)))))
(defn random-string-list
[list-length string-length]
(doall (take list-length (repeatedly (partial random-string string-length)))))
(def orc-names (random-string-list 3000 7000))
Because map and pmap are lazy, we have to force them to be realized. We donât want the result to be printed to the REPL, though, because that would take forever. The dorun function does just what we need: it realizes the sequence but returns nil:
(time (dorun (map clojure.string/lower-case orc-names)))
; => "Elapsed time: 270.182 msecs"
(time (dorun (pmap clojure.string/lower-case orc-names)))
; => "Elapsed time: 147.562 msecs"
The serial execution with map took about 1.8 times longer than pmap, and all you had to do was add one extra letter! Your performance may be even better, depending on the number of cores your computer has; this code was run on a dual-core machine.
You might be wondering why the parallel version didnât take exactly half as long as the serial version. After all, it should take two cores only half as much time as a single core, shouldnât it? The reason is that thereâs always some overhead involved with creating and coordinating threads. Sometimes, in fact, the time taken by this overhead can dwarf the time of each function application, and pmap can actually take longer than map. Figure 10-3 shows how you can visualize this.
We can see this effect at work if we run a function on 20,000 abbreviated orc names, each 300 characters long:
(def orc-name-abbrevs (random-string-list 20000 300))
(time (dorun (map clojure.string/lower-case orc-name-abbrevs)))
; => "Elapsed time: 78.23 msecs"
(time (dorun (pmap clojure.string/lower-case orc-name-abbrevs)))
; => "Elapsed time: 124.727 msecs"
Now pmap actually takes 1.6 times longer.
The solution to this problem is to increase the grain size, or the amount of work done by each parallelized task. In this case, the task is to apply the mapping function to one element of the collection. Grain size isnât measured in any standard unit, but youâd say that the grain size of pmap is one by default. Increasing the grain size to two would mean that youâre applying the mapping function to two elements instead of one, so the thread that the task is on is doing more work. Figure 10-4 shows how an increased grain size can improve performance.
To actually accomplish this in Clojure, you can increase the grain size by making each thread apply clojure.string/lower-case to multiple elements instead of just one, using partition-all. partition-all takes a seq and divides it into seqs of the specified length:
(def numbers [1 2 3 4 5 6 7 8 9 10])
(partition-all 3 numbers)
; => ((1 2 3) (4 5 6) (7 8 9) (10))
Now suppose you started out with code that looked like this:
(pmap inc numbers)
In this case, the grain size is one because each thread applies inc to an element.
Now suppose you changed the code to this:
(pmap (fn [number-group] (doall (map inc number-group)))
(partition-all 3 numbers))
; => ((2 3 4) (5 6 7) (8 9 10) (11))
There are a few things going on here. First, youâve now increased the grain size to three because each thread now executes three applications of the inc function instead of one. Second, notice that you have to call doall within the mapping function. This forces the lazy sequence returned by (map inc number-group) to be realized within the thread. Third, we need to ungroup the result. Hereâs how we can do that:
(apply concat
(pmap (fn [number-group] (doall (map inc number-group)))
(partition-all 3 numbers)))
Using this technique, we can increase the grain size of the orc name lowercase-ification so each thread runs clojure.string/lower-case on 1,000 names instead of just one:
(time
(dorun
(apply concat
(pmap (fn [name] (doall (map clojure.string/lower-case name)))
(partition-all 1000 orc-name-abbrevs)))))
; => "Elapsed time: 44.677 msecs"
Once again the parallel version takes nearly half the time. Just for fun, we can generalize this technique into a function called ppmap, for partitioned pmap. It can receive more than one collection, just like map:
(defn ppmap
"Partitioned pmap, for grouping map ops together to make parallel
overhead worthwhile"
[grain-size f & colls]
(apply concat
(apply pmap
(fn [& pgroups] (doall (apply map f pgroups)))
(map (partial partition-all grain-size) colls))))
(time (dorun (ppmap 1000 clojure.string/lower-case orc-name-abbrevs)))
; => "Elapsed time: 44.902 msecs"
I donât know about you, but I think this stuff is just fun. For even more fun, check out the clojure.core.reducers library (http://clojure.org/reducers/). This library provides alternative implementations of seq functions like map and reduce that are usually speedier than their cousins in clojure.core. The trade-off is that theyâre not lazy. Overall, the clojure.core.reducers library offers a more refined and composable way of creating and using functions like ppmap.
Summary
In this chapter, you learned more than most people know about safely handling concurrent tasks. You learned about the metaphysics that underlies Clojureâs reference types. In Clojure metaphysics, state is the value of an identity at a point in time, and identity is a handy way to refer to a succession of values produced by some process. Values are atomic in the same way numbers are atomic. Theyâre immutable, and this makes them safe to work with concurrently; you donât have to worry about other threads changing them while youâre using them.
The atom reference type allows you to create an identity that you can safely update to refer to new values using swap! and reset!. The ref reference type is handy when you want to update more than one identity using transaction semantics, and you update it with alter! and commute!.
Additionally, you learned how to increase performance by performing stateless data transformations with pmap and the core.reducers library. Woohoo!
Exercises
- Create an atom with the initial value 0, use
swap!to increment it a couple of times, and then dereference it. - Create a function that uses futures to parallelize the task of downloading random quotes fromhttp://www.braveclojure.com/random-quote using
(slurp "http://www.braveclojure.com/random-quote"). The futures should update an atom that refers to a total word count for all quotes. The function will take the number of quotes to download as an argument and return the atomâs final value. Keep in mind that youâll need to ensure that all futures have finished before returning the atomâs final value. Hereâs how you would call it and an example result:(quote-word-count 5) ; => {"ochre" 8, "smoothie" 2} - Create representations of two characters in a game. The first character has 15 hit points out of a total of 40. The second character has a healing potion in his inventory. Use refs and transactions to model the consumption of the healing potion and the first character healing.
The print book longs for you to own it
OMG what!? Another book!?
Great mama of the bahamas! Learn about reducers!