By Developers, For Developers

Historical errata for Seven Concurrency Models in Seven Weeks

PDF PgPaper PgTypeDescriptionFixed onComments
vOK

“In 1989 I started …”

A nod to Grace Hopper might not be out of place; she was pushing concurrency way before 1989.

I’d also like to see a table that shows which concurrency and parallelism
approaches are supported (and to what extent) by various languages.

So, actors, agents, queues, reducers (etc) would be on one axis;
Clojure, Erlang, Go, and Ruby (etc) would be on the other. I’d
expect Clojure to have pretty complete coverage; Ruby, not so much.

2014-04-21
79ERROR

The definition of the translate function is missing a closing parenthesis (has three, should have four).

2013-11-07
80ERROR

The definition of the translate function is missing a closing parenthesis (has three, should have four).

2013-11-07
93TYPO

I’m reading a PDF. The page number printed on the page is 93, but it is page 102 of the file. The error occurs in the first sentence. “A validator is a function that is called whenever there’s an attempt to change the value the atom.” should be, “A validator is a function that is called whenever there’s an attempt to change the value of the atom.”

2013-11-07
53TYPO

public int sum(int[] numbers) {
int accumulator = 0;
for (int n: numbers)
accumulator += n;
return n;
}
should return accumulator, not n.

2013-11-26
85TYPO

“Clojure provides a similar hybrid of functional programming with and mutable state”

Probably only need one of “with and”.

2013-12-01
59ERROR

“Here’s count-words. Given a sequence of pages, it returns a map of the frequencies of the words in those pages: […] It starts by converting the sequence of pages into a sequence of words with (mapcat get-words pages).”

This example does not use `mapcat`. It also introduces pmap, which seems odd considering that pmap is parallel and this example should be sequential; pmap is also talked about in the self-study.

On page 63, this code is referred to again and is explicitly said to be sequential

2013-12-01Thanks Jake, \n \nAs you'll see in the next beta update, this chapter has been significantly rewritten. Some of those changes leaked into the last update, for which apologies. Hopefully it should now make sense again. \n \nThanks!
85SUGGEST

May want to add references to core.async[1] and pulsar[2].

The former is “A Clojure library designed to provide facilities for async programming and communication.”, the latter is “Pulsar is a Clojure library that provides high-performance lightweight threads and Erlang-like actors”.

[1]: github.com/clojure/core.async
[2]: puniverse.github.io/pulsar/

2013-12-01As it happens, we've recently decided to add a chapter that covers core.async (in place of the previously planned Tuple Spaces chapter). Look for it in a beta update before the end of the year.
108TYPO

The body text references on the page refer to function name with trailing exclamation marks, !, but the code examples to not have them.

2013-12-01Thanks Bo!
105TYPO

Before: Checking = 10000 Savings = 20000
Attempts: 638
Transfers: 300
After: Checking = 20000 Savings = 10000

The “Transfers” count should be 200.

2013-12-14Thanks Daniel, \n \nThe error's actually in the description - it says that one thread transfers $100 100 times, and the other $200 100 times. In fact the second thread actually transfers $100 200 times. \n \nThanks for pointing it out - I'll fix the description in the next beta.
108TYPO

#53263 not fixed in B4.0. Still exclamation mark where they don’t belong.

2013-12-23Thanks Bo. I fixed a couple of these, but also missed a couple. Sorry about that (and thanks for pointing it out again).
54ERROR

Where Does Reduce Get Its Initial Value? From the Clojure docs[1]:

“If val is not supplied, returns the result of applying f to the first 2 items in coll”

The identity usefulness comes into play with the reducers library, however.

[1]: clojuredocs.org/clojure_core/clojure.core/reduce

2014-01-18Good point - I'll move this discussion to the section on reducers rather than reduce. Thanks. \n \nUpdate: I ended up changing the description to "What If We Pass An Empty Vector To reduce?" for which the identity value is used, even in vanilla reduce. Thanks again Jake.
56SUGGEST

“We start by creating an array”
“inserting the result of ​(range 0 10000000)​ into an empty array ”
(other examples in Clojure code)

Clojure tends to refer to [] as a “vector”. “array” might be confusing, as Clojure tends to reserve that term for Java primitive arrays. However, Java also has “Vector”, so both are potentially confusing. :-) I’d stick with the Clojure terms to help people when searching later.

2014-01-18Good point. Will do. Thanks.
56OK

“As is often the case with code running on the ​JVM​, we have to run more than once”

You may want to use or mention the generally-approved Clojure benchmarking library `criterium`: github.com/hugoduncan/criterium

2014-04-21Thanks. I'm a big fan of Criterium, but wanted to keep things simple for the reader here. I'll discuss this with Jackie (my editor) and see what she thinks. Thanks again.
61ERROR

“Arguments are specified with %1, 2, and so on, which can be further shortened to a single if each argument is used exactly once and in order.”

`%` is an alias for `%1`, but should only be used when there is exactly one argument:

(map #(+ %1 %2) [1 2] [3 4])
;; => (4 6)

(map #(+ % %) [1 2] [3 4])
;; => clojure.lang.ArityException: Wrong number of args (2) passed to: user$eval865$fn

2014-01-18Ah! Thank you, I had assumed that % in Clojure worked like _ in Scala. Clearly not. Thanks again.
174TYPO

First line read:
“We can’t discuss Clojure macros in detail here here, so…”
should be
“We can’t discuss Clojure macros in detail here, so …”

2014-03-07
23SUGGEST

Shouldn’t the l2 unlock be in the given finally block and the l1 unlock be in a finally block of the outside try block?

2014-03-22Sigh. Yeah, strictly it should be - this was a misguided attempt on my part to do something to make the code more comprehensible in the face of Java's ludicrous checked exceptions. \n \nThe problem is that I'm trying to get the code to exit cleanly, and if I write the strictly correct code it doesn't (it throws an IllegalMonitorStateException before it gets a chance to process the InterruptedException, because the unlock() fails.). The "correct" code ends up being a maze of twisty try/catches which obscures the basic point. \n \nI suspect that the right solution is to ditch the unlock calls entirely - otherwise I'm confusing matters more than I'm helping. \n \nThanks!
25SUGGEST

Could you put lock numbers on the hand-over-hand diagram? This might make the concept of lock over the next one more clear. Also, is there a reason why you didn’t start the diagram by locking the head node?

2014-04-13Hmm - won't the lock numbers just be the index of the node in the list? Am I missing something? \n \nAs for why didn't I start at the head node, I guess I didn't really think about it particularly carefully - I just started a couple of steps before the point where I was going to end up inserting the new node :-) How confusing did your reading group find this?
29OK

Should the “stop eating” code be removed from the think() method and put at the end of the eat() method? (Or be moved to its own method?). Currently think() violates the SRP.

2014-04-21Whether it violates the SRP depends on how you think of the "stop eating" code. Each philosopher is a very simple state machine with two states. If you think of the code that you describe as "stop eating" as "start thinking", then the SRP holds :-) \n \nI'm slightly wary of moving this code into eat(), as that method then gets rather long. And I'm slightly wary of creating another method as it's already (thanks to Java's crazily verbose syntax) rather longer than I'd prefer. And f I move "stop eating" into its own method, I probably also need to move "start eating" into its own method, making things even more verbose...
32SUGGEST

Suggestion to add to the self-study: what would happen if there are multiple conditions in your code that signal out of order? (Trying to get at the concept here of conditions not having memory.)

2014-04-16I'm not 100% sure I understand what you're getting at here, David - could you elaborate?
65OK

(defprotocol CollReduce
(coll-reduce [coll f] [coll f init]))

should be

(defprotocol CollReduce
(coll-reduce [coll f] [coll init f]))

2014-04-21Thanks Siva, \n \nCan you explain why you think it's the wrong way around a little further? The Clojure source has it the way that's currently used in the book: \n \nhttps://github.com/clojure/clojure/blob/master/src/clj/clojure/core/protocols.clj#L17 \n \nHave I missed something?
40ERROR

In the full Counter.java linked from page 40, the lock is declared local to each Counter class so it doesn’t work as written. To fix the bug, change the declaration to private static ReentrantLock lock;

2014-04-20Man! How did I miss that? Thanks David - much appreciated. \n \n(in case you're wondering, this was a bug introduced when "reformatting" the code to make it look better on the page - unfortunately, I ended up doing rather more than "reformatting").
165TYPO

“That means that a go block compares very favorably to an Elxir process—a very impressive result given that Clojure runs on the JVM, whereas Elixir runs on the Erlang virtual machine, which was built with efficient concurrency in mind.”

“Elixr” should be “Elixir”.

2014-05-22
28TYPO

In the “Joe asks” box on p28, “voilate” should be “violate”.

2014-05-22
141TYPO

Half way down page 141, on the right-hand side, “fault-tolerate” should be “fault-tolerant”.

2014-05-22
113TYPO

First line at the top of page 113: “claim-chopstics!” is missing the “k” in chopsticks.

2014-06-16
77OK

if you get the count of words, do some instructions and then replace the wordcount by count + addedCount you are leaving the door open for other threads to increment the wordcount while you are replacing it with an old incremented wordcount…

2014-06-26Forgive me, Filip, I'm not sure exactly which bit of the book you're talking about here. Page 77 doesn't contain any wordcount code? Are you reporting a bug that you've found in one of the examples? If so, which one? I'd be very grateful if you could provide more context. Thanks!
XOK

It seems that my e-reader is giving me different pages then you are able to see so I will give more information:

I am starting from the following point in Chapter 2; day 3 and will be commenting inline:

“Happily, we’re not beaten yet. ConcurrentHashMap in java.util.concurrent looks like exactly what we need. Not only does it provide atomic read-modify-write methods, but it’s been designed to support high levels of concurrent access (via a technique called lock striping). Here’s a modified countWord that uses ConcurrentHashMap:”

The following code is being run with many consumers so many threads will be writing to the counts hash map…

private​ ​void​ countWord(​String​ word) {
while​ (true) {

///////////////////// Take a current count for the number…
​Integer​ currentCount = counts.get(word);
​ \t
​if​ (currentCount null) { ​ \t /////////// Insert the number if it isn't in the hash map, so far so good... ​if​ (counts.putIfAbsent(word, 1) null)
​break​;
​ \t
/////////// Replace the number in the hash map with the count we fetched a few instructions earlier with the chance of other threads replacing in between…
/////////// At this point you run in to the chance that another dozen thread will have beaten you by incrementing, so when you increment you replace it with an incorrect number, there is nothing atomic about the value you push.
/////////// The only guaranty you have is that only one will be writing to the hash map position, not that it will be blocked during the entire method where you fetch it in the beginning and replace it at the end…
} ​else​ ​if​ (counts.replace(word, currentCount, currentCount + 1)) {
​ \t
​break​;
​ \t
}
​ \t
}
​ \t
}

2014-06-26
6TYPO

In the sentence starting with
“By handing multiple connections concurrently” replace “handing” with “handling”.

2014-06-25
78ERROR

Is: server.core=> (sentence-split “This is a sentence. Is this?! A fragment”)
Should be: server.sentences=> (sentence-split “This is a sentence. Is this?! A fragment”)

sentence-split is not available in the server.core namespace.
Alternatively you can refer to sentence-split from server.core in the source code

104ERROR

Java style comment (// Side-effect in transaction - DON’T DO THIS) causes
java.lang.RuntimeException: Invalid token: //

Can be replaced with ; Side-effect in transaction - DON’T DO THIS

163ERROR

The book doesn’t specify a clojure or core.async version, so I’m using the latest.

Dependencies:
[org.clojure/clojure “1.6.0”]
[org.clojure/core.async “0.1.338.0-5c5012-alpha”]

The following function will no longer compile. The recur must be the last form in the go-loop. I made it work by changing the when-let to an if-let and closing the channel on the “else”. My understanding is that Clojure enforces this as it does not perform TCO when compiling.

(defn get-primes [] (let [primes (chan)
numbers (to-chan (iterate inc 2))] (go-loop [ch numbers]
(when-let [prime (<! ch)]
(>! primes prime)
(recur (remove< (partial factor? prime) ch)))
(close! primes))
primes))

Thanks,

Tim

Also, thanks for the book! It’s really helped me understand core.async better.

190TYPO

There is a broken image on the page in the Pipelining section. I can see the image in the epub version of the book.

66SUGGEST

Was really enjoying the book till I got to the reducible section, in particular the my-map example. I really think that example is out of context with whats gone before and I found it as hard to follow as any example I can ever think of seeing in a practical programming book.

Its unfortunate as reducers seem subtle but also quite interesting, but even after studying the code quite a bit I really can’t work out whats going on. Obviously this will partly be because I’ve never really used Clojure, but even allowing for that I think the example could do with some work. The variable names chosen (reducef, f1, v) don’t help but I also think it might be worth a diagram or some overall explanation of the interactions between the functions here.

Ta,

Colin

80SUGGEST

I think a diagram and clear text explanation of the HTTP requests that are going on would definitely help make the final example easier to follow.

In addition in this case it might be worth adding an explanation of the “get-translation” function as I think it would be quite easy for the reader to think they’ve understood it without fully following.

94SUGGEST

For people who are reading this on a tablet or e-reader it might be useful if functions like new-session had a comment explaining what initial is, once you know its a map its much easier for non-Clojure dev to see what’s going on. I know its not the sort of comment you’d want in real code in a dynamic language but I still think it would be useful here.

112TYPO

I think you mean atom in this bullet point and the one before:

“The choice between an STM and an agent-based solution is largely one of
style and performance characteristics.”

99ERROR

(send my-agent #((Thread/sleep 2000) (inc %)))

is resulting in either an NullPointerException or in no increment with my environment (
Leiningen 2.5.0 on Java 1.7.0_51 Java HotSpot™ 64-Bit Server VM -> Clojure 1.6.0)

changing it to

(send my-agent #( do (Thread/sleep 2000) (inc %) ) )

seems to work for me. I am not a Clojure expert but the problematic part seems to be the Thread.sleep(long) method that return void

user=> (Thread/sleep 2000)
nil

The explicit do seems to be the solution here.

104TYPO

transfers is an agent and earlier you say calling await waits for all actions from current thread to complete, however in the -main method you have to call transfers even though it is the other 2 threads that interact with it. I thought it might be worth explaining why as it seems interesting.

1SUGGEST

As well as covering FRP I think it might be worth having a chapter on node.js, mainly because its single-threaded nature make it interesting and impose restrictions that are perhaps worth discussing.

35SUGGEST

Since the book only requires 100,000 pages from the Wikipedia dump, I’d suggest using the Simple Wikipedia dump [1]. It comes with 200,000 pages, and is only 100MB compressed. This is much easier to set up than the full English version at 10GB compressed.

Tim

[1]: dumps.wikimedia.org/simplewiki/

58SUGGEST

Some Wikipedia dumps return ‘nil’ for certain pages, resulting in null pointer exceptions when ‘get-words’ is called. An easy fix is to have ‘get-words’ check that its input isn’t nil.

(defn get-words [text]
(if-not (nil? text) ; or ‘(if text’ if that’s your style.
(re-seq #“\\w+” text)))

35ERROR

`’luaxlib.h’ file not found` on OSX with new install of lua via homebrew.

Tried running cmake with `include_directories(/usr/local/include)` both included and not included in `CMakeLists.txt`.

169TYPO

In `poll-fn` and `poll`, the local variable `seconds` is mis-named; it’s actually milliseconds.

163ERROR

I ran into basically the same issue as report #77231. Where go-loop/recur examples are not working. Using the map-chan example I have found that if you switch from using go-loop to (go (loop…) (close! ..)) you can get the code working.

(defn map-chan [f from]
\t(let [to (chan)]
\t\t(go
\t\t\t(loop []
\t\t\t\t(when-let [x (<! from)]
\t\t\t\t\t(>! to (f x))
\t\t\t\t\t(recur)))
\t\t\t(close! to))
\t\tto))

99ERROR

The problem with “#((Thread/sleep 2000) (inc %))” is that Clojure expects a form in “#()”, not a sequence. The “do” at the start of the form passes a sequence of forms, which is why “#(do (Thread/sleep 2000) (inc %))” works.

27SUGGEST

In the ConcurrentSortedList class you traverse a list backwards when you calculate list size.

Is there a particular reason you do it this way? Did you want to show that it will not deadlock? Would it be also ok if we went forward?

4026ERROR

ConcurrentSortedList example is flawed in my opinion.

If a thread is interrupted before the next lock is acquired, “next.lock.unlock();” will throw IllegalMonitorStateException.
We could fix it by checking if we acquired the lock
if (next.lock.isHeldByCurrentThread()) {
next.lock.unlock();
}

More serious problem is that if we are interrupted after “next = current.next;” then we never release the next lock, since it points to the next node already.

It also doesn’t follow the pattern described when you introduce ReentrantLock’s (unlocks are swapped in your code):
Lock lock = new ReentrantLock();
lock.lock();
try {
«use shared resources»
} finally {
lock.unlock();
}

I found your explanation why you implemented it this way to keep the code clear, but this version is buggy and should be improved or replaced by another example.

40ERROR

Might be missing something, but I think that the ConcurrentHashMap example has a race condition.
Assume two threads reach line 3 in ‘countWord’ for the same ‘word’ parameter. Both get ‘currentCount’ as null. Both go down the ‘putIfAbsent’ condition, and one of the ‘putIfAbsent’ call succeeds, setting the corresponding word’s count to 1. The other thread’s call does nothing, and we get an initial count of 1, rather than 2.

16ERROR

Hi, this is a good book for java developers. I write here just want to get a confirm about the right understanding about the sample code.

At the end of Chapter 2, about the alien methods, I think the example for defensive copy is incorrect. In fact, the clone operation of listeners only cause a shallow copy for the ArrayList. As a result, listenersCopy and listeners still share the same elements in them, even though they are two separate ArrayLists. So I think it can not guarantee the effect stated below the sample code.

If I have any mistake please feel free to correct me.

Thanks!

45TYPO

‘distributed shared-memory’ should be ‘distributed-memory’.

Categories: