I just read the excellent blog C++ and the Perils of Double-Checked Locking
And I don't understand why we have to use the first memory barrier in Example 12 (as below):
Singleton* Singleton::instance () {
Singleton* tmp = pInstance;
... // insert memory barrier
if (tmp == 0) {
Lock lock;
tmp = pInstance;
if (tmp == 0) {
tmp = new Singleton;
... // insert memory barrier
pInstance = tmp;
}
}
return tmp;
}
Is it safe to change it to code below? Why not?
Singleton* Singleton::instance () {
if (pInstance == 0) {
Lock lock;
if (pInstance == 0) {
Singleton* tmp = new Singleton;
... // insert memory barrier
pInstance = tmp;
}
}
return pInstance;
}
No, it isn't safe. Reading the three paragraphs before the example, and the two after it, the potential problem is a system where the write to pInstance
is done (flushed to memory) on thread B before the construction of Singleton
has been flushed. Then thread A could read pInstance
, see the pointer as non-null, and return it potentially allowing thread A to access the Singleton
before thread B has finished storing it into memory.
The first flush is necessary to ensure that the flushing of the writes during construction of Singleton
have been completed before you try to use it in a different thread.
Depending on the hardware you're running on this might not be a problem.