Re: Please disprove this Double-Checked Locking "fix"
On May 1, 5:14 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
On 4/26/2011 10:50 AM, Pete Becker wrote:
pinstance points to. The C++0x solution is:
#include <atomic>
std::atomic<Singleton*> pinstance;
if (pinstance == 0) {
Lock lock;
if (pinstance == 0)
pinstance = new Singleton;
}
return pinstance;
Making pinstance an atomic variable ensures that all writes that "happe=
n
before" the assignment to pinstance in one thread are seen before the
result of that assignment is seen in any other thread.
True, but that's not enough to make this code work. You also need to
know that assignment to pinstance can't take place until the Singleton
constructor has run to completion. You have that guarantee in C++0x,
too, so the code is correct, but it's not just the use of atomics that
gives you that correctness. You also need the additional guarantees
that C++0x offers regarding the behavior of new expressions (compared to
the weaker guarantees that C++03 gives you).
I don't think I'd describe it that way.
Consider:
pinstance = new Singleton;
For a simple pointer in C++03, there are no threading guarantees
given, so of course it can implement it however it wants, which is
apparently frequently enough something like the following pseudo-code
pinstance = malloc(sizeof(Singleton));
new(pinstance) Singleton();
Even under POSIX, to detect the difference between the above and the
following pseudo-code
temp = malloc(sizeof(Singleton));
new(temp) Singleton();
pinstance = temp;
would require code that has a race condition, which means undefined
behavior, which means that the compiler can do whatever it wants.
In C++0x, the situation is unchanged for a simple pointer. There are
no guarantees specific to "new expressions" which change this. The
compiler is allowed to implement it in exactly the same way. If you
have some code that could detect this, then you still have a race
condition, and thus undefined behavior, and thus all bets are off, so
the compiler free to do as it wants.
What does change is when we throw std::atomic into the mix. When
pinstance is of type std::atomic, the following:
pinstance = new Singleton;
is equivalent to the following ala operator overloading pseudo-code
(forgive me for not knowing the specific function name offhand):
pinstance.set(new Singleton);
And C++0x does have very specific guarantees concerning std::atomic.
It doesn't matter that the argument is a new expression. It could be
any sort of expression or function call. There is no guarantee
specific to new expressions. There are specific guarantees relating to
std::atomic::set and std::atomic::get, and there are guarantees that
describe all expressions, but not new expressions in particular.
Perhaps you were talking about the guarantees specific to function
local static variables? C++0x does guarantee that their initialization
is properly synchronized so that the first access will initialize it,
and all other threads will wait until it's done, and all other threads
will properly see the initialized function local static variable.
However, Pete Becker's code does not use a function local static, and
even then the guarantees about function local statics are not specific
to new expressions. You could initialize one with a normal function
call or some other expression which isn't a new expression, and the
same guarantees apply.