Reputation: 10490
I read somewhere that a singleton was thread-unsafe. I'm trying to understand why this is. If I have a singleton object like this:
class singleton final
{
public:
static singleton& instance()
{
static singleton unique;
return unique;
}
private:
singleton() = default;
singleton(singleton const&) = delete;
singleton& operator=(singleton const&) = delete;
};
And if I have code like this:
singleton *p1, *p2;
auto t1 = std::thread([] { p1 = &singleton::instance(); });
auto t2 = std::thread([] { p2 = &singleton::instance(); });
t1.join();
t2.join();
Is it possible for p1
and p2
to point to two different singleton
instances? If unique
is static
, does its "static" nature not take affect until it's fully initialized? If that's so, does that mean that a static object's initialization can be accessed concurrently and thus allowing the creation of multiple static objects?
Upvotes: 1
Views: 168
Reputation: 219428
In C++98/03 a file local static:
X& instance()
{
static X x;
return x;
}
meant that your code would do something like this:
bool __instance_initialized = false;
alignas(X) char __buf_instance[sizeof(X)];
// ...
X& instance()
{
if (!__instance_initialized)
{
::new(__buf_instance) X;
__instance_initialized = true;
}
return *static_cast<X*>(__buf_instance);
}
Where the "__"-prefixed names are compiler supplied.
But in the above code, nothing is stopping two threads from entering the if
at the same time, and both trying to construct the X
at the same time. The compiler might try to combat that problem by writing:
bool __instance_initialized = false;
alignas(X) char __buf_instance[sizeof(X)];
// ...
X& instance()
{
if (!__instance_initialized)
{
__instance_initialized = true;
::new(__buf_instance) X;
}
return *static_cast<X*>(__buf_instance);
}
But now it is possible for one thread to set __instance_initialized
to true and start constructing the X
, and have the second thread test and skip over the if
while the first thread is still busy constructing X
. The second thread would then present uninitialized memory to its client until the first thread finally completes the construction.
In C++11 the language rules were changed such that the compiler must set up the code such that the second thread can not run past, nor start the construction of X
until the first thread successfully finishes the construction. This may mean that the second thread has to wait an arbitrary amount of time before it can proceed ... until the first thread finishes. If the first thread throws an exception while trying to construct X
, the second thread will wake up and try its hand at constructing it.
Here is the Itanium ABI specification for how the compiler might accomplish that.
Upvotes: 11