I’ve just finished reading this book, and at 950 pages of text it has taken quite some time. I’ve been a fan of Joe Duffy’s blog for a long while and couldn’t wait for this book to come out. It is, quite simply, superb, going into good technical depth about concurrency as it relates to both the Win32 and the CLR platforms. It seems to cover everything, from the ways to manage program state, to the platform available threads, synchronisation objects, mutexes, condition variables and thread pools. From the asynchronous programming model to fibers. From the memory model to concurrency hazards. From performance and scalability to parallel containers and data and task parallelism. It also has chapters on the parallel extensions for .NET and a chapter on designing reusable libraries for .NET programs.
I’m really keen to get into the low level details of the CLR. In the past, one of the really good blogs was that of Chris Brumme, which gave good information about why features were implemented in a particular way. Joe Duffy’s book does the same, linking the Win32 feature and its CLR abstraction with details about how the abstraction is implemented – for example, he covers how the CLR Wait mechanisms need to do lots of work to deal with incoming APCs which cause the wait to terminate at the Win32 level, requiring a rescheduling of the wait at the CLR level. I enjoyed the whole book, but for me the most interesting chapter was the one covering the .NET memory model. This covers in detail the guarantees regarding the reordering of reads and writes that various compilers and jitters realise – in particular, stating the kinds of fences that the system places around the various synchronisation primitives and the meaning of volatile. Many blogs have debated the validity of the double-check lock pattern; this book offers definitive answers.
This book really pulls concurrency into the 21st century. I remember spending time on my degree course understanding algorithms like those of Dekker which implements critical sections using only atomicity of reads and writes. Early in the book, Duffy explains that the reasoning behind these algorithms is invalid on modern hardware where loads and stores may be reordered and memory caches may contain old values which will be refreshed for some time. It is only by having a memory model at both the compiler and hardware level that we can easy reason about such lock free programs. The book points out that lock free programming is very hard to get right – most of the time people should be using lock which implements a full fence at the memory model level. The book covers the Task Parallel Library which offers higher level constructs for getting tasks to run in parallel and easy mechanisms for synchronising the tasks when they finish; this library also offers easy methods for cancelling tasks that are running using cooperative termination.
Appendix A also offers some guidelines on designing concurrent reusable libraris for .NET and contains some really good insights.
This is a book that I am going to reread many times.