CSC/ECE 517 Fall 2009/wiki2 14 conc patterns
Thread-Safe Programming and Concurrency Patterns
Introduction
Within a particular realm, design patterns represent solutions to the problems that are encountered during the development phase of software development life cycle(SDLC). These patterns help in reusing of successful software architectures and designs. There are various different patterns as shown in adjacent figure.
1. Double-checked locking
The Double-checked locking design pattern is used when an application has one or more critical sections of code that must execute sequentially; when multiple threads can potentially attempt to execute the critical section simultaneously; when the critical section is executed just once and when acquiring a lock on every access to the critical section causes excessive overhead.
Many familiar design patterns such as Singleton that work well for sequential programs contain subtle assumptions that do not apply in the context of concurrency. Consider the following implementation of the Singleton pattern:
class Singleton { public: static Singleton *instance (void) { if (instance_ == 0) // Critical section. instance_ = new Singleton; return instance_; } void method (void); // Other methods and members omitted. private: static Singleton *instance_; }; // ... Singleton::instance ()->method (); // ...
The above implementation does not work in the presence of preemptive multi-tasking or true parallelism. For instance, if multiple threads executing on a parallel machine invoke Singleton::instance simultaneously before it is initialized, the Singleton constructor can be called multiple times This violates the properties of a critical section.
The Solution: Double-Checked Locking
A common way to implement a critical section is to add a static Mutex to the class which ensures that the allocation and initialization of the Singleton occurs atomically. A Guard class is used to ensure that every access to Singleton::instance will automatically acquire and release the lock as follows:
class Singleton { public: static Singleton *instance (void) { // First check if (instance_ == 0) { // Ensure serialization (guard // constructor acquires lock_). Guard<Mutex> guard (lock_); // Double check. if (instance_ == 0) instance_ = new Singleton; } return instance_; // guard destructor releases lock_. } private: static Mutex lock_; static Singleton *instance_; };
The first thread that acquires the lock will construct Singleton and assign the pointer to instance_. All threads that subsequently call instance will find instance_!= 0 and skip the initialization step. The second check prevents a race condition if multiple threads try to initialize the Singleton simultaneously. Thus, the Double-Checked Locking pattern provides advantages of minimized locking and prevention of race conditions.