A singleton ensures that only one object of a certain type exists throughout the program. Double-checked locking is a common, efficient way to initialize a singleton in multithreaded applications. The following code illustrates such an implementation.
100 class Singleton { 101 public: 102 static Singleton* instance(); 103 ... 104 private: 105 static Singleton* ptr_instance; 106 }; 107 108 Singleton* Singleton::ptr_instance = 0; ... 200 Singleton* Singleton::instance() { 201 Singleton *tmp; 202 if (ptr_instance == 0) { 203 Lock(); 204 if (ptr_instance == 0) { 205 tmp = new Singleton; 206 207 /* Make sure that all writes used to construct new 208 Singleton have been completed. */ 209 memory_barrier(); 210 211 /* Update ptr_instance to point to new Singleton. */ 212 ptr_instance = tmp; 213 214 } 215 Unlock(); 216 } 217 return ptr_instance;
The read of ptr_instance on line 202 is intentionally not protected by a lock. This makes the check to determine whether or not the Singleton has already been instantiated in a multithreaded environment more efficient. Notice that there is a data race on variable ptr_instance between the read on line 202 and the write on line 212, but the program works correctly. However, writing a correct program that enables data races requires extra care. For example, in the above double-checked-locking code, the call to memory_barrier() at line 209 is used to ensure that ptr_instance is not seen to be non-null by the threads until all writes to construct the Singleton have been completed.