Re: What is the problem with writing singleton in multithreaded enviroment
Timo Geusch wrote:
Ronen Yacov wrote:
When declaring a singleton, we write:
static CMySingle::instance()
{
static CMySingle instance;
return instance;
}
If I interpret your code correctly, this is not a proper singleton. But
I'm just guessing, seeing that the function has no return type.
Well, since he says it's a singleton, I think we can assume that
the return type is CMySingle& (or CMySingle const&). In which
case, it's pretty much one of the consacrated single threaded
versions of a singleton.
Why should there be a problem in a multithreaded enviroment?
Because there's a potential race condition if/when two threads call
instance() at the same time and thus initialising instance twice.
Maybe. The real problem is that 1) C++ doesn't say anything
about it, because C++ doesn't address threading, 2) Posix (or
other threading standards) doesn't say anything about it,
because they only address C, and there's no equivalent construct
in C, and 3) most compiler writers don't seem to care, and
pretend that the problem doesn't exist.
Given 1) and 2), it's implementation defined. G++ defines it,
and there's no problem. Most other compilers don't, and in the
absence of a concrete guarantee, you have to assume that there
may be a race condition (and actually is if more than one thread
calls the function before construction is completed in many
implementations).
If one thread creates a static object the other threads will
have the same instance (becuase its static).
That's not the problem. The problem is that there is nothing in your
code that prevents instance from being initialised twice, at the same
time.
Which may or may not be a problem, depending on the compiler.
And if there is a problem, how the docuble check locking solves it?
Without resurrecting the discussion in another thread, *locking* per se
mostly solves the problem because it provides a mechanism to serialise
the check if the singleton has already been created.
Double checked locking doesn't really apply in your case anyway but
would normally look like this C++-like pseudo code:
foo* bar::get_foo_instance()
{
if (!foo_instance) {
lock.aquire();
if (!foo_instance) [
foo_instance = new foo;
}
lock.release();
}
}
Except, of course, that this code doesn't work. foo_instance
can appear non-null before all of the writes in the constructor
are visible in a given thread. In order to make double checked
locking work, it's generally necessary to insert special
assembler instructions (membar, fence, etc.) at critical places.
The double check is necessary because in the above code, foo_instance
may have been initialised while the second was waiting for the lock,
This is a performance optimisation as aquiring the lock is usually a
fairly expensive operation and not necessary if foo_instance is already
initialised.
The idea that aquiring a lock is somehow expensive seems to be
widespread, but it is definitly false. Under Solaris, aquiring
a lock costs less than a function call, at least according to
the measurements I made. From discussions in other threads,
this is a general characteristic of Posix locks, and is
apparently true for CriticalSections under Windows as well.
Aquiring a lock is only expensive if someone else is holding it,
and this will only be the case, statistically, if someone holds
the lock for a significant amount of time.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orientie objet/
Beratung in objektorientierter Datenverarbeitung
9 place Simard, 78210 St.-Cyr-l'Icole, France, +33 (0)1 30 23 00 34
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]