Re: Double checked locking pattern article on aristeia
On 08/17/2011 09:51 PM, nospam wrote:
There is something in this article that puzzles me
http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
The article says the following code may not work correctly in a
multi-threaded environment for two reasons. The code has volatile all
over the place to prevent the compiler from re-ordering code. The
idea is to avoid acquiring the (expensive) Lock every time you need to
access the singleton.
class Singleton {
public:
static volatile Singleton* volatile instance();
//...
private:
//
static volatile Singleton* volatile pInstance;
};
// from the implementation file
volatile Singleton* volatile Singleton::pInstance = 0;
volatile Singleton* volatile Singleton::instance() {
if (pInstance == 0) {
Lock lock;
if (pInstance == 0) {
volatile Singleton* volatile temp =
new volatile Singleton;
pInstance = temp;
}
}
return pInstance;
}
The first reason given for why this code may fail in a multi-threaded
environment is given on page 10
<quote>
First, the Standard?s constraints on observable behavior are only for
an abstract machine defined by the Standard, and that abstract machine
has no notion of multiple threads of execution. As a result, though
the Standard prevents compilers from reordering reads and writes to
volatile data within a thread, it imposes no constraints at all on
such reorderings across threads. At least that?s how most compiler
implementers interpret things. As a result, in practice, many
compilers may generate thread-unsafe code from the source above.
<end quote>
I can't figure out what the above quoted text is getting at. Can
anyone explain? What does "re-ordering across threads" mean?
It means, for example, that if thread A creates a Singleton
and thread B sees pInstance != 0, it might not see the insides
of the Singleton initialised. Why? Because thread B does not call
anything that would prevent reordering, so it might see pInstance
first and Singleton insides initialised later.
Volatile does not help here: it is only for the compiler, not for
the CPU, and modern CPUs do reorderings just like the compiler
if not worse. It depends what CPU; on some CPUs this code would be
working correct 100% of the time. For a CPU 'volatile' is when
a page of 'memory' has special access flags, meaning it is not
RAM but actually some device register of e.g. in PCI slot.
The 'volatile' in code _and_ memory mapping make things right,
but for threads volatile (almost) never helps in anything
except good feeling of the programmer.
Since the CPUs do not prevent that, the standard says nothing,
compilers assume they don't have to. They can't reorder
across any opaque call like locking the lock, but in thread B's
case this does not happen. They won't reorder a volatile
with another volatile, that's it. So:
take a singleton, that has
static int i; // defined somewhere, initialisation sets it
int doSomething () {
return i;
}
Assuming it inlines the following code (instance() not being inline
does not change anything in this regard, same for doSomething):
int x = instance()->doSomething();
It might become, in pseudo-assembly:
put Singleton::i in x
put pInstance to r1
if r1 == 0:
do the lock, init singleton,
set pInstance, unlock, put i to x again
else:
# that's it, compiler is happy...
# ...but the programmer
# is not so happy, because apparently x got a value
# from uninitialised Singleton::i
# once a week in tests
It is correct, the result is 'as-if' the instructions
were in order, for a single thread.
Also, initialisation inside the Singleton might be
in a different order, might happen after assignment to
pInstance. And this is just with a single int,
imagine a much bigger Singleton - you'll never be
able to debug it, you will just know it is incorrect.
Later in the article they address it by making everything in Singleton
volatile, and sum up all the 'volatile' crap at the end.
Last but not least, volatile does not prevent thread B
seeing just one half of pointer pInstance filled by A and second
half still zero.
Edek
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]