jason.cipriani@gmail.com
12/3/2008 4:19:00 AM
On Dec 2, 9:17 pm, Sam <s...@email-scan.com> wrote:
> jason.cipri...@gmail.com writes:
> > static Mutex mutex;
> > static TheClass *instance;
>
> > static TheClass * getInstance () {
> > MutexLocker lock(mutex);
> > if (!instance)
> > instance = new TheClass();
> > return instance;
> > }
>
> > The example then goes on to talk about how double-check locking is
> > broken, etc. My question is pretty much this: Is C++ static
> > initialization thread-safe? If not, then how does the above example
> > safely use "mutex"? If so, then what is wrong with this:
>
> > static TheClass instance; // not a pointer
>
> > static TheClass * getInstance () {
> > return &instance; // it's correctly initialized?
> > }
>
> > The reason I ask is I almost never see it done like that, I always see
> > blog entries and articles that say the same thing "store instance in a
> > pointer, use a mutex to protect, and p.s. double-checked locking is
> > broken". It seems like doing it lock-free is made out to be a hard
> > problem, so *if* having a static instance works (but I don't know if
> > it does, that's my question), then why doesn't anybody ever suggest
> > it?
>
> Setting aside the fact that there's no such thing as threads or mutexes in
> the C++ language (at least not yet), so you are using a platform specific
> library here.
I just used "Mutex" and "AutoMutex" as an example.
> Your statically declared instance gets constructed at some unspecified point
> before your main() function gets invoked. If you have other objects in
> static scope, it is unspecified the order in which all your static instances
> get initialized. This may be undesirable. It's possible that it is necessary
> to construct your singleton in a more controlled fashion, after all your
> other objects, in static scope or otherwise, get initialized. Using a
> dynamically-allocated pointer to your singleton, and protecting it with a
> mutex, gives you the means to accomplish that.
I see. So, it's safe to use a global-scoped static instance for the
singleton instance, as long as you don't need *precise* control over
when it's initialized (just as long as it's initialized before it's
used)? Even if it's accessed from different translation units (and
defined in a different one than main() is in)?
I did an experiment with VS 2008 where I made the singleton class's
constructor Sleep() for 2 seconds hoping to make a race condition
occur, and did this:
=== A.h ===
class A {
public:
A ();
~A ();
static A * getInstance ();
};
=== A.cpp ===
static A theInstance;
A * A::getInstance () { return &theInstance; }
I had main() in a different source file, and it created some threads
with functions in a 3rd source file. I called A::getInstance() in each
of those threads, and saw that theInstance was initialized before main
() was even entered, and everything worked great.
Is this standard behavior that I can rely on, or is it specific to the
MS compiler?
I also tried making theInstance function-scoped, in the getInstance()
function. That didn't work, I guess there's different rules for
function-scoped static initialization (I did read that, and also read C
++0x makes some guarantees about it). I noticed that if I created
multiple threads like this:
threadproc () {
A * a = A::getInstance();
}
The first thread created waited the 2 seconds as the A() was
constructed, but every thread after that immediately returned, *with*
the pointer, but before the A() constructor had returned.
A * A::getInstance () {
static A theInstance;
return &theInstance;
}
I guess that makes sense. "&theInstance" is already known, so threads
can return immediately while it's still being constructed.
The third thing I tried was storing theInstance at class scope, and
using a pointer but statically initializing it with new(). That also
worked, it was initialized before main() was entered. Is this also
behavior that can be relied on? E.g.:
class A {
static A * getInstance ();
...
static A * theInstance;
};
A * A::theInstance = new A();
A * A::getInstance () { return theInstance; }
So, global scope worked, class scope worked, function scope was all
messed up.
Thanks,
Jason