While working on a solution for a fairly narrow and specific problem, I discovered a new synchronization mechanism. This mechanism not only provided a novel and effective solution to my problem, but turns out to have a number of properties making it useful for a wide range of important applications.
My original problem had to do with the implementation of Singletons. The issues with Singletons involve the order and time of their initialization and destruction, as well as the thread safety of these operations. Despite the multitude of techniques developed to deal with these issues, there does not seem to be one universal solutionlet alone a simple one.
The particular Singleton I was working on had to be located in a low-level library, which was used by many applications and other libraries. This constraint ruled out the use of a C++ file-scope variable as a mechanism for initializing the Singleton because there is no way to ensure that initialization of such an object is done properly before its first use. To avoid the active collaboration that would result from explicit initialization, I concluded that the Singleton had to be initialized on first access. The Meyers Singleton pattern (Listing One) fits this requirement. When this pattern is employed, the Singleton is initialized the first time it is used, and automatically destroyed when the process terminates. A word of caution: It is sometimes necessary not only to ensure that a Singleton is destroyed at process termination, but also to establish a particular relative order; for my Singleton, such was not the case.
class Singleton { Singleton(); ~Singleton(); public: static Singleton& instance(); }; Singleton& Singleton::instance() { static Singleton singleton; return singleton; }
Listing One
This implementation of a Singleton leaves the issue of thread safety unresolved. If multiple threads enter the instance
routine simultaneously and if this routine has never been executed before, various problems may occur, depending on the compiler and platform. For example, the constructor of my Singleton may be executed multiple times or not at all. Alternatively, the constructor may be executed exactly once, which is correct, but one of the instance
routines may return before the construction is completedagain, leading to unpredictable and hard-to-debug failures. As you'd expect, you can eliminate this race condition by using one of a number of various synchronization mechanisms. Listing Two, for example, employs a mutex. In Listing Two, I did not show how the mutex variable is initialized. The mutex itself is a Singleton! If this mutex has a nontrivial constructor or destructor, we have a chicken-and-egg problem.
class Singleton { Singleton(); ~Singleton(); static Singleton *s_singleton; static void init(); public: static Singleton& instance(); }; Singleton *Singleton::s_singleton = 0; void Singleton::init() { static Singleton singleton; s_singleton = &singleton; } Singleton& Singleton::instance() { lock(&mutex); if (!s_singleton) { init(); } unlock(&mutex); return *s_singleton; }
Listing Two
UNIX: pthread Solutions
Solving the problem of initializing the mutex on UNIX platforms isn't difficult. The pthread library available on these platforms provides a mutex that is a Plain Old Data (POD) type requiring only static (nonruntime) initialization, as in Listing Three. Additionally, the pthread library provides a mechanism called "once
routines" that can also be used to solve this initialization problem (Listing Four).
Singleton& Singleton::instance() { static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&mutex); if (!s_singleton) { init(); } pthread_mutex_unlock(&mutex); return *s_singleton; }
Listing Three
Singleton& Singleton::instance() { static pthread_once_t once_control = PTHREAD_ONCE_INIT; pthread_once(&once_control, &Singleton::init); return *s_singleton; }
Listing Four