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]

Re: pthread_once() problem?


On Tue, 2 Nov 1999, Joerg Faschingbauer wrote:

> Date: Tue, 2 Nov 1999 12:13:49 +0100
> From: Joerg Faschingbauer <jfasch@hyperwave.com>
> Reply-To: glibc-linux@ricardo.ecn.wfu.edu
> To: glibc-linux@ricardo.ecn.wfu.edu
> Subject: 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.

It's too bad I don't read that these days. Finding a news server
has gotten problematic. ;)

> 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.

This problem has been discussed in comp.programming.threads in the past.  The
concensus is that it's basically safe on mainstream architectures.

You don't have to worry about it because it is buried in the implementation of
a system library, which is by definition platform specific. If it ever doesn't
work on a particular target, the library maintainers will have to patch
in a code variant to support that platform and you can happily continue to
use pthread_once.

> 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;

Note that if access to the *once control is not atomic, this technique still works.
The *once_control variable is only ever written to once and starts with the
value zero. Until the write completes, it's not possible for it to have 
any other value, and once it has a value other than zero, the init routine
has been called.  If the value is inspected  after the init routine is done,
but before *once_control is written to, then it will come up with the value
zero, and the lock will be acquired. 

What about multiprocessor platforms which reorder reads and writes, and hence
require memory barriers? The risk in such an environment would be that the
shared data initialized by the init_routine() is not yet stable when the
*once_control variable indicates DONE. However, note that there is a
pthread_mutex_lock() operation between the call to init_routine() and
the assignment to *once_control. This pthread_mutex_lock() would introduce
the requisite memory barrier, ensuring that the initialized data is stable
before the *once_control modification appears on any other processor.

Note also that this is different from the C++ example given later in your
posting.  In that example, no memory barrier intervenes between the read and
the write. But one could be inserted, like this:

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

Now, the memory barrier ensures that the memory writes carried out by
the construction of Singleton are seen on all processors before the
update of the instance_ static.

Hope this helps!


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