Path: csiph.com!aioe.org!.POSTED!not-for-mail From: Kaz Kylheku <217-679-0842@kylheku.com> Newsgroups: comp.programming.threads Subject: Re: pthread_once_t in dynamically allocated (and freed) memory? Date: Mon, 26 Feb 2018 19:14:01 +0000 (UTC) Organization: Aioe.org NNTP Server Lines: 108 Message-ID: <20180226105333.543@kylheku.com> References: <4a411f41-1032-4f7b-8148-5d2249029820@googlegroups.com> <20180225181853.893@kylheku.com> NNTP-Posting-Host: hlQwqVQo951TVKyinY4JRg.user.gioia.aioe.org Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Complaints-To: abuse@aioe.org User-Agent: slrn/pre1.0.0-18 (Linux) X-Notice: Filtered by postfilter v. 0.8.3 Xref: csiph.com comp.programming.threads:4062 On 2018-02-26, dave@boostpro.com wrote: > On Sunday, February 25, 2018 at 6:29:49 PM UTC-8, Kaz Kylheku wrote: >> On 2018-02-25, Dave Abrahams wrote: >> > Hi, >> > >> > I want to dynamically allocate memory, then once, on demand, thread-safely initialize part of that memory, and potentially free the memory. Then later I want to repeat the process with what is potentially the same memory block (and thus the same address of pthread_once_t) that has been returned from the allocator. Does this >> > >> > a. "just work" as long as it is set up with PTHREAD_ONCE_INIT, or >> > b. do I need to do insert some kind of fence to ensure that all threads see the re-initialized memory, or >> > c. never work; don't do that! >> > d. something else I haven't thought of? >> >> It must work. > > Well, I realize that's what the docs seem to imply, but… The docs clearly require it; if it doesn't work, it's an implementation bug. >> Firstly, POSIX says that PTHREAD_ONCE_INIT is a constant. >> Thus it may be used in assignment. This is important because, by >> contrast, the PTHREAD_MUTEX_INITIALIZER is described as a macro >> that can be used for initialization, and not a constant. > > Sure, but when I assign it, I am doing nothing to ensure that assignment is visible to all threads before they call pthread_once on it. Therefore it seemed plausible that another core's cache still contains some other value for that memory location, and a thread running on that core and calling pthread_once would see the uninitialized value instead of PTHREAD_ONCE_INIT, and skip calling the initialization function, which would be a problem… This depends on how the threads acquire the pointer to this entire object that contains the "pthread_once_t". Suppose that we dynamically allocate "struct point { int x, y; }" and initialize x and y, then pass this on to other threads. How do we ensure that the memory is synchronized so that threads see the stable values of x and y? Whatever approaches works for these x and y is applicable to the initialization of a pthread_once_t. If we just allocate an object, initialize it, put its pointer into an unprotected shared variable which another thread notices has become non-null and uses it, and do not execute any memory barriers, then we have a problem on some machines. If some synchronization is involved then we should be okay. E.g. consumer is waiting on a condition variable for the pointer to materialize and then uses it. static obj *ptr; // assumed initially null // producer new_object->once = PTHREAD_ONCE_INIT; pthread_mutex_lock(&mut); ptr = new_object; pthread_mutex_unlock(&mut); pthread_cond_broadcast(&cond); // consumer pthread_mutex_lock(&mut); while (ptr == 0) pthread_cond_wait(&cond, &mut); pthread_mutex_unlock(&mut); pthread_once(&ptr->once, init_fun); Here, the memory synchronizing properties of the mutex ensure that all the assignments done prior to the producer's pthread_mutex_unlock call are visible at the time the consumer acquires the mutex and finds ptr not to be null. The consumer cannot find a non-null ptr, such that ptr->once has not yet been initialized. If you don't use synchronization like this when introducing the pointer to the other threads, then you're on your own; but it's not really an issue of the pthread_once, but rather an issue of the the reordering of the loads and stores of "ptr" and "ptr->once". >> Secondly, there is a storage restriction on the phread_once_t >> control variable: namely, it may not be defined in automatic storage >> (i.e. non-static local variable). There is no restriction against >> pthread_once_t being in dynamic storage. >> >> > I ask because all the examples I can find store the pthread_once_t in >> > static storage and never re-initialize it. >> >> However, if that is in a shared library, it's dynamic anyway. The >> dynamic case is effectively exercised any time a shared library is >> dlopen'ed that has a file scope pthread_once_t. > > Hm, you make a good point. But you know how tricky races are; I > wouldn't count on all that exercise to have flushed out a bug if this > issue wasn't totally considered. But, come to think of it, is this situation really protected in all circumstances? Suppose thread A obtains a handle with dlopen, passes it unsafely to thread B (e.g. just puts it into a shared location where B picks it up, no mutexes). Is it guaranteed that B can use dlsym, etc, to read the stable value of a global variable variable? I think, if so, it's only because some mutexes are incidentally used inside dlopen, like to insert the library into some global list of loaded libraries. [I apologize for this topical interruption and now return you to the regular deluge of drivel by the local troll.]