Multithreading and the C++ Type System
Introduction
Multithreaded programming is so troublesome from both a theoretical and practical viewpoint that it certainly would not have continued as a valid programming paradigmwere it not for its formidable payoff in efficiency.
On shared-memory multiprocessor machines, multithreading is the best, most natural way to exploit machine resources. Plus, many interactive programs spend a significant amount of time waiting for keyboard/mouse inputtime that can be used to perform useful ancillary tasks (precomputing hidden edges of a 3D graphic, gathering document statistics, and so on). This explains why multithreading is so attractive, even though, if true parallelism is not present, a multithreaded program will always be slower than its single-threaded equivalent.
Despite its attractiveness, multithreading has consistently escaped formalization attempts. After Dijkstra's seminal work1, little theoretical progress has been made in perfecting efficient and portable threading models, despite a significant accumulation of practical experience.
Attempts to integrate threads in programming languages have registered a pale success at best. More often than not, they offer limited idioms and inefficient implementations. To date, multithreaded programs that need to be efficient use the same mutexes, semaphores, and events that Dijkstra described 35 years ago, seasoned with platform-specific low-level idiosyncrasies.
This unfortunate state of affairs makes multithreaded programs difficult to design, debug, ensure correct, optimize, maintain, and analyze formally. Consequently, building tools that automate detection of race conditions and deadlocks is highly desirable.
Recent research papers2, 3 describe how to create type systems that make it easier to detect multithreading-specific errors. For example, a race condition will cause a type error that is detected at compile time.
An article of mine4 describes practical compile-time race condition detection in C++. The method exploits the volatile keyword not as a semantic vehicle, but only as a participant to the C++ type system. The programmer qualifies the shared user-defined objects with volatile. Those objects can be manipulated only by applying a const_cast. Finally, a helper object can ensure that the const_cast is performed only in conjunction with locking a synchronization object. Effectively, an object's type (volatile-qualified or not) depends on whether its corresponding synchronization object is locked or not. The main caveat of the technique is that the use of the obscure volatile qualifier might appear confusing to the unwitting maintainer.
The following text describes more C++ idioms that exploit the C++ type system to achieve detection of race conditions at compile time.