This was a fascinating read. Haskell itself has a reputation for being very high level and abstract, and, of course, this book uses some very high level concepts to achieve multithreaded execution and deterministic parallelism. However the author was one of the main implementers for the runtime system of GHC, and this comes across in the writing – from the use of ThreadScope to see exactly where the threads and runtime are spending their time to the brilliant explanation of weak-head normal form, deepseq and the execution semantics using sparks.
GHC has a great number of ways of using multiple threads. The book starts with parallel Haskell, covering rpar, rseq and the Eval monad which allows an algorithm to be parameterised with different evaluation strategies. All the time, the author demonstrates the facility using an example, and often demonstrates how well things are working by measuring the speedup with number of cores that the runtime is allowed to use. Sometimes these measurements point to bottlenecks in the runtime itself and he isn’t afraid to point this out.
Next the book moves on to concurrent Haskell.
The author starts by covering threads communicating via MVars (which are like data values protected by a semaphore). He then moves on to discussing Exceptions, a facility of Haskell that I had largely ignored in the past. At this point, things starts getting little dirty as we need to start thinking about asynchronous exceptions, and that means that we need to be able to guard regions in order to prevent asynchronous exceptions happening in things like finally blocks which would make it impossible to preserve invariants.
This is the thing that I always found distasteful in C# where thread abort exceptions, which are asynchronously delivered as a result of the Thread.Abort, can be caught but require special code to prevent them propagating after the catch handler has finished.
There’s also the need to prevent the delivery inside finally blocks.
The GHC runtime requires the programmer to duplicate these semantics themself by exposing primitives for controlling asynchronous delivery.
The book then moves on to software transactional memory. It shows how beautiful code can become using this feature. Using retry, for example, allows the STM system to wait until the global state changes before restarting failed computations. Better still, the author points out the possible problems with STM for large transactions where the transaction log access and multiple potential retries can make performance really bad.
The rest of the book is filled with some examples, including a chat server. These examples demonstrate a number of techniques, including a function which can be used as a combinator that races two threads and returns the answer from the first to complete, aborting the other.
The book is a great read, and I think everyone will learn something from it.