Is Meyers' implementation of the Singleton pattern thread safe?
Solution 1:
In C++11, it is thread safe. According to the standard, §6.7 [stmt.dcl] p4
:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
GCC and VS support for the feature (Dynamic Initialization and Destruction with Concurrency, also known as Magic Statics on MSDN) is as follows:
- Visual Studio: supported since Visual Studio 2015
- GCC: supported since GCC 4.3
Thanks to @Mankarse and @olen_gam for their comments.
In C++03, this code wasn't thread safe. There is an article by Meyers called "C++ and the Perils of Double-Checked Locking" which discusses thread safe implementations of the pattern, and the conclusion is, more or less, that (in C++03) full locking around the instantiating method is basically the simplest way to ensure proper concurrency on all platforms, while most forms of double-checked locking pattern variants may suffer from race conditions on certain architectures, unless instructions are interleaved with strategically places memory barriers.
Solution 2:
To answer your question about why it's not threadsafe, it's not because the first call to instance()
must call the constructor for Singleton s
. To be threadsafe this would have to occur in a critical section, and but there's no requirement in the standard that a critical section be taken (the standard to date is completely silent on threads). Compilers often implement this using a simple check and increment of a static boolean - but not in a critical section. Something like the following pseudocode:
static Singleton& instance()
{
static bool initialized = false;
static char s[sizeof( Singleton)];
if (!initialized) {
initialized = true;
new( &s) Singleton(); // call placement new on s to construct it
}
return (*(reinterpret_cast<Singleton*>( &s)));
}
So here's a simple thread-safe Singleton (for Windows). It uses a simple class wrapper for the Windows CRITICAL_SECTION object so that we can have the compiler automatically initialize the CRITICAL_SECTION
before main()
is called. Ideally a true RAII critical section class would be used that can deal with exceptions that might occur when the critical section is held, but that's beyond the scope of this answer.
The fundamental operation is that when an instance of Singleton
is requested, a lock is taken, the Singleton is created if it needs to be, then the lock is released and the Singleton reference returned.
#include <windows.h>
class CritSection : public CRITICAL_SECTION
{
public:
CritSection() {
InitializeCriticalSection( this);
}
~CritSection() {
DeleteCriticalSection( this);
}
private:
// disable copy and assignment of CritSection
CritSection( CritSection const&);
CritSection& operator=( CritSection const&);
};
class Singleton
{
public:
static Singleton& instance();
private:
// don't allow public construct/destruct
Singleton();
~Singleton();
// disable copy & assignment
Singleton( Singleton const&);
Singleton& operator=( Singleton const&);
static CritSection instance_lock;
};
CritSection Singleton::instance_lock; // definition for Singleton's lock
// it's initialized before main() is called
Singleton::Singleton()
{
}
Singleton& Singleton::instance()
{
// check to see if we need to create the Singleton
EnterCriticalSection( &instance_lock);
static Singleton s;
LeaveCriticalSection( &instance_lock);
return s;
}
Man - that's a lot of crap to "make a better global".
The main drawbacks to this implemention (if I didn't let some bugs slip through) is:
- if
new Singleton()
throws, the lock won't be released. This can be fixed by using a true RAII lock object instead of the simple one I have here. This can also help make things portable if you use something like Boost to provide a platform independent wrapper for the lock. - this guarantees thread safety when the Singleton instance is requested after
main()
is called - if you call it before then (like in a static object's initialization) things might not work because theCRITICAL_SECTION
might not be initialized. - a lock must be taken each time an instance is requested. As I said, this is a simple thread safe implementation. If you need a better one (or want to know why things like the double-check lock technique is flawed), see the papers linked to in Groo's answer.