1

I was reading a paper on "Perils of double check locking by Scott Meyers". http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

The author gives reasons why the double check lock fails (page 3, section 4). I was thinking of ways to get around this problem without using C++11. Not that I don't want to use C++ 11 but just to see if it is possible to solve it without using functions such as std::call_once, etc

class Singleton {
public:
    static Singleton* instance();

private:
    static Singleton* pInstance;
    int someData_;
};


Singleton* Singleton::instance() 
{
    class SeqAssign{
    public:
        SeqAssign(Singleton*& pInst, Singleton* pNew):
        pInstance(pInst), pNewedInst(pNew){
        }
        ~SeqAssign(){
            pInstance = pNewedInst;
        }

    private:
        Singleton*& pInstance;
        Singleton*  pNewedInst;
    };

    if (pInstance == 0) { // 1st test
        Lock lock;
        if (pInstance == 0) { // 2nd test
            SeqAssign seq(pInstance, new Singleton);
        }
    }
    return pInstance;
}

Will the code in Singleton::instance() work in a multi-threaded environment as the order in which the constructor and destructor is called for the SeqAssign class is deterministic.

Xeo
  • 129,499
  • 52
  • 291
  • 397
Ram
  • 3,045
  • 3
  • 27
  • 42
  • 5
    The workaround is "Don't use a singleton". – Puppy Sep 10 '12 at 09:15
  • you are right, sometimes, singleton is called an anti-pattern and it is recommended to avoid it. Here the question is not singleton, it is about how to make double check locking work. – Ram Sep 10 '12 at 09:20
  • 2
    If you don't want C++11 stuff, don't tag the question as such. I retagged to `[c++03]`. – Xeo Sep 10 '12 at 09:23
  • @Ram Not by any of the experts I know. The almost universal consensus is that it's a useful pattern for a few specific uses (but that it can be horribly abused). – James Kanze Sep 10 '12 at 09:23
  • On the other hand, I've never understood what double checked locking brings to the singleton pattern. It's easy to ensure that the singleton is constructed before entering `main`, and you really shouldn't be starting threads before entering `main`. – James Kanze Sep 10 '12 at 09:24
  • 1
    Actually, "before entering main" is kind of complicated since you often need to establish an order of things, and those 'before main' things pretty much happen in randomish order. I generally do a bit of both: I collect a list of "things to do" with priorities to establish the correct ordering before main, and work through that list early in main. – Christian Stieber Sep 10 '12 at 09:27
  • Singletons are helpful - just do not overuse them. My guess would be `Lock lock;` is not the same lock for different threads - you probably have to have common mutex for them in struct. – Pawel Zubrycki Sep 10 '12 at 09:28
  • @Pawel: Yes, lock is representative code. Also, this question is not to figure out singleton implementation. I would like to know how to make the double check lock work – Ram Sep 10 '12 at 09:36

1 Answers1

2

No. The variable pInstance is accessed from more than one thread. It is modified. The code has undefined behavior. And I'm not sure what you thing SeqAssign will do, since it doesn't introduce any additional inter-thread sequencing.

There is no way to make double checked logging work in standard C++; all of the solutions involve atomic variables (possibly implemented using inline assembler) or thread local storage.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • TLS and atomics are part of Standard C++ now. You might want to revise that to Standard C++03. – Puppy Sep 10 '12 at 09:24
  • James, can you please elaborate why? Here, the destructor call is deterministic and it has to be at the end of the scope. Does C++ not guarantee RAII. If it does, by the same logic, should this not work ? – Ram Sep 10 '12 at 09:28
  • 1
    @Ram: It's not an observable side effect- that is, the optimizer is free to inline and shift it around. – Puppy Sep 10 '12 at 09:37
  • @DeadMG The original poster asked about non-C++11. I should have been specific that that was the standard I was referring to. – James Kanze Sep 10 '12 at 10:09
  • @Ram RAII only works within a single thread. There's nothing to guarantee that other threads see the writes in the same order as they occur in the writing thread. – James Kanze Sep 10 '12 at 10:12
  • 1
    @DeadMG Not just the compiler. The order values appear in global memory is not necessarily the order the store instructions appear in the compiler generated assembler. Hardware does a lot of reordering today, and is why you need explicit synchronization even if you turn off all compiler optimization. – James Kanze Sep 10 '12 at 10:16