Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.programming.threads > #1331

Re: Double check locking - are release and acquire fences enough for C++ memory model?

Date 2013-02-06 14:39 +0100
From Marcel Müller <news.5.maazl@spamgourmet.org>
Newsgroups comp.programming.threads
Subject Re: Double check locking - are release and acquire fences enough for C++ memory model?
References <9b219fb9-0b51-4c06-9398-d1e7ab878f7d@googlegroups.com> <511207c5$0$6565$9b4e6d93@newsspool3.arcor-online.net> <225a96b2-e794-43c7-b82a-64a69c2cb626@hq4g2000vbb.googlegroups.com>
Message-ID <51125d0d$0$6572$9b4e6d93@newsspool3.arcor-online.net> (permalink)
Organization Arcor

Show all headers | View raw


On 06.02.13 13.40, Michael Podolsky wrote:
>> I am a bit confused. None of these atomic operations are required if we
>> are talking about double check. The idea of double checking is to use an
>> ordinary mutex and to provide a fast path for some frequent cases.
>> Whether the fast path has a race condition or not is unimportant because
>> the mutex will ensure defined behavior.
>
> Let's stop here because if we do not agree about the need of memory
> fences in double check locking fast path, the rest is irrelevant.
> http://erdani.com/publications/DDJ_Jul_Aug_2004_revised.pdf (chapter
> 6) gives some argumentation why memory fences are needed, so is
> http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
> (though related to Java).

You are right. Maybe I did too much x86 and x64 the last time. Memory 
reordering is very limited there so I did not run into this pitfall so far.
Additionally I wrote the pseudo code a bit to fast to make it look more 
pretty. Usually it looks slightly different and avoids any unnecessary 
access to the shared storage.

static T* ptr = NULL;
static Mutex mtx;

T* get_singleton_T()
{ T* tmp = ptr;
   if (tmp)         // First check, race condition accepted
     return tmp;
   mtx.lock();      // wait for mutex, full mem bar
   tmp = ptr;
   if (tmp)         // the double check
   { mtx.release(); // release mutex, full mem bar
     return tmp;
   }
   tmp = new T();   // create singleton
   mtx.release();   // release mutex, full mem bar
   return ptr = tmp;
}

This prevents the accidental access to uninitialized members of T.
The side effect, that the change to ptr may never propagate to other 
CPUs only causes some additional lock/release.

Maybe there are platforms where Mutex lock/release does not ensure cache 
coherence. In this case you are right and a synchronized access is 
always required, even in case the object has been initialized long before.
But I can's see why release/acquire should /not/ be sufficient in this 
cases. So to reply to your original question, I do not see a scenario 
where the use of two dependent singletons with double checked 
initialization end up in undefined behavior as long as each of the 
double checks is implemented correctly.

Of course, one race will never disappear. If writing or reading a 
pointer is not implicitely atomic, then double check makes no sense for 
pointers. In this case even memory barriers won't help. But in the last 
25 years I have never developed on a platform where this was not 
satisfied. MC68k, Alpha, T80x, x86-32 and x86-64 met the condition. And 
with platforms without atomic pointer access one could use an additional 
boolean to keep the initialized flag.


In practice I have some use cases where the singleton is immutable, 
reproducible and only for performance reason of singleton type. (Things 
like string.Empty) In this cases I use an even more ugly version without 
any locking:

class T
{ static T ptr = null;
   public T instance
   { get
     { if (!ptr)
         ptr = new T();
       return ptr;
     }
   }
}

Of course, this makes only sense for languages with a GC like .NET. But 
with the costraint that every new T() will produce a logically 
equivalent object and, of course, pointers are implicitly atomic, it 
will not result in undefined behavior. In worst case it may create as 
many objects as there are threads and/or CPUs. But this can still be 
only a slight overhead compared to thousands of objects created otherwise.


Marcel

Back to comp.programming.threads | Previous | NextPrevious in thread | Next in thread | Find similar


Thread

Double check locking - are release and acquire fences enough for C++ memory model? Michael Podolsky <michael.podolsky.69@gmail.com> - 2013-02-04 23:09 -0800
  Re: Double check locking - are release and acquire fences enough for C++ memory model? Marcel Müller <news.5.maazl@spamgourmet.org> - 2013-02-06 08:35 +0100
    Re: Double check locking - are release and acquire fences enough for C++ memory model? Michael Podolsky <michael.podolsky.69@gmail.com> - 2013-02-06 04:40 -0800
      Re: Double check locking - are release and acquire fences enough for C++ memory model? Marcel Müller <news.5.maazl@spamgourmet.org> - 2013-02-06 14:39 +0100
        Re: Double check locking - are release and acquire fences enough for C++ memory model? Michael Podolsky <michael.podolsky.69@gmail.com> - 2013-02-06 19:19 -0800
    Re: Double check locking - are release and acquire fences enough for C++ memory model? Gerhard Fiedler <gelists@gmail.com> - 2013-02-06 11:58 -0200
  Re: Double check locking - are release and acquire fences enough for C++ memory model? Michael Podolsky <michael.podolsky.rrr@gmail.com> - 2013-04-01 12:12 -0700

csiph-web