This is the mail archive of the glibc-linux@ricardo.ecn.wfu.edu mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

pthread_once() problem?


Hi,

last week I posted a note to comp.programming.threads. I quote the
note below. It entailed an interesting thread (well, a news-thread
:-), IMO.

I believe the LinuxThreads pthread_once() implementation has a similar
problem. I.e., the test without holding the lock, in the first line of
the body. The memory init_routine() wrote (whatever that is) is the
equivalent of the Singleton instance in the quoted note.

Opinions?
 
int __pthread_once(pthread_once_t * once_control, void (*init_routine)(void))
{
  /* Test without locking first for speed */
  if (*once_control == DONE) return 0;
  /* Lock and test again */
  pthread_mutex_lock(&once_masterlock);
  /* If init_routine is being called from another routine, wait until
     it completes. */
  while (*once_control == IN_PROGRESS) {
    pthread_cond_wait(&once_finished, &once_masterlock);
  }
  /* Here *once_control is stable and either NEVER or DONE. */
  if (*once_control == NEVER) {
    *once_control = IN_PROGRESS;
    pthread_mutex_unlock(&once_masterlock);
    init_routine();
    pthread_mutex_lock(&once_masterlock);
    *once_control = DONE;
    pthread_cond_broadcast(&once_finished);
  }
  pthread_mutex_unlock(&once_masterlock);
  return 0;
}


Joerg


PS: Here's the post...

From: Joerg Faschingbauer <jfasch@hyperwave.com>
Subject: Double checked locking and non-sequential memory model?
Newsgroups: comp.programming.threads
Date: 29 Oct 1999 16:25:52 +0200
Organization: Graz University of Technology, Austria
Path: fstgal00.tu-graz.ac.at!not-for-mail
Lines: 80
Sender: jfasch@hwiw01.hyperwave.com
Message-ID: <861zaes0pb.fsf@hwiw01.hyperwave.com>
NNTP-Posting-Host: fiicm2pc60.tu-graz.ac.at
X-Newsreader: Gnus v5.5/Emacs 20.2

Hi,

I just came across the "double checked locking" described by Doug
Schmidt (somewhere in
http://www.cs.wustl.edu/~schmidt/ACE-papers.html), and believe there
is a problem which I try to explain below. Please correct me if I am
missing something (I assume I do).

The double checked locking reads about as follows.

class Singleton {
public:
        Singleton* instance() ;

private:
        Mutex mutex_ ;
        static Singleton* instance_ ;
} ;

Singleton* Singleton::instance() {
        if (! instance_) {
                lock_.acquire() ;
                if (/*still not*/ !instance_)
                        instance_ = new Singleton ;
                lock_.release() ;
        }
        return instance_ ;
}

The intent of this is to not need to expensively lock every access to
instance_, but rather only in the case where the instance_ is not yet
set. If you didn't lock at all, you had a race: two or more threads
could end up setting instance_ multiple times. So far so good.

I am no expert in memory models, but it seems to me there is a problem
when you have CPUs that employ a non-sequential memory model (SPARCs
do, and I believe Alphas as well). I.e. store instructions can be
reordered arbitrarily by the processor.

When a thread A (on processor A) walks through the instance() above
and does not see the instance_ set, it has to create a new Singleton
and set instance_, which are essentially (at least) two disjoint write
operations. The lock_.release() adds a memory barrier to guarantee
that if another thread B (on processor B) acquires the lock later on
it also sees instance_ and the correctly initialized Singleton object
in main memory (note that this does not imply that both already be
written at the time of lock_.release()).

But what if thread B does not acquire the lock_ but rather applies the
double checked locking? What does that mean?

If thread B sees instance_==0 in main memory all is well. Thread B has
to acquire the lock, and then either sees instance_ because thread A
has set it (and thus sees the Singleton object that instance_
references, beacause that is guaranteed by the barrier), or it still
does not see it (in which case it may safely set it).


If it sees instance_!=0 then what does that mean? It means that 

(1) thread B has already passed the instance() "if" branch and has set
instance_

and

(2) that instance_ has been written to main memory (else thread B
wouln't see it)

Thread B however does *not* check the lock, hence cannot safely assume
that *everything* has been written to main memory (it didn't see the
lock). Especially, the Singleton object might not have been written to
main memory and thus might be still uninitialized.

See what I mean? Thread B might end up reading bogus data from the
uninitialized Singleton object it assumes to be initialized.




Joerg


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]