Reputation: 5085
(Code taken from book: http://gameprogrammingpatterns.com/ by Robert Nystrom)
In the book above the author comes with two different ways of making a singleton class:
First one:
class FileSystem
{
public:
static FileSystem& instance()
{
// Lazy initialize.
if (instance_ == NULL) instance_ = new FileSystem();
return *instance_;
}
private:
FileSystem() {}
static FileSystem* instance_;
};
And second one:
class FileSystem
{
public:
static FileSystem& instance()
{
static FileSystem *instance = new FileSystem();
return *instance;
}
private:
FileSystem() {}
};
Later he states that the second one is more proper way to do this, since it is thread safe, while the first one is not.
What makes the second one thread safe?
What is the difference in static declarations between these two?
Upvotes: 2
Views: 85
Reputation: 30489
In former case, if two threads try to create the instance at the same time, 2 (or more) copies of singleton objects might be created. (if **instance_**
is observed NULL by both and both create a new
instance). (Even worse, thread creating first instance may get a different value of instance in subsequent calls)
While second one uses static
initialization and object is constructed when the function is invoked for the first time. So it is guaranteed by the compiler that static FileSystem *instance = new FileSystem();
would be executed at most once during the lifetime of program single and thus atmist single copy of object would exist at any time.
This makes the later design thread safe for C++11 and onwards complian C++ compilers. Though the design may not be safe for in C++03 and C++98 implementation.
Another downside of former design is, the object can not be destructed while in later design it can be destructed by changing the typeof instance_
to static FileSystem
. i.e.
static FileSystem& instance()
{
static FileSystem instance;
return instance;
}
Related: Is Meyers implementation of Singleton pattern thread safe?
Upvotes: 1
Reputation: 14987
The first version is not thread-safe for multiple threads may try to read and modify instance_
concurrently without any synchronization, which leads to a race condition.
The second version is thread-safe since C++11. Quoted from cppreference (the Static local variables section):
If multiple threads attempt to initialize the same static local variable concurrently, the initialization occurs exactly once (similar behavior can be obtained for arbitrary functions with std::call_once)
With this guarantee, modification to instance
happens only once, and there is no problem with concurrent reads.
Still, the second version is not thread-safe until C++11.
Upvotes: 3
Reputation: 726929
In the first code snippet setting the instance_
pointer to a singleton is an assignment. It does not get any special treatment from the compiler. In particular, it could be done multiple times if instance()
is invoked from concurrent threads.
This creates a problem when two concurrent threads try evaluating instance_ == NULL
, and get a true
. At this point both threads create a new instance, and assign it to the instance_
variable. The first pointer assigned to instance_
is leaked, because the second thread immediately overrides it, rendering the object inaccessible.
In the second code snippet setting the instance
pointer is initialization. The compiler guarantees that initialization of a static variable will be done at most once, regardless of the number of threads that invoke instance()
concurrently. Essentially, the guarantee that there is at most one singleton in the system is provided by the compiler, without any explicit code for handling concurrency.
Upvotes: 1