* starvation in pthread_once? @ 2005-03-03 9:32 Gottlob Frege 2005-03-04 23:18 ` Ross Johnson 0 siblings, 1 reply; 15+ messages in thread From: Gottlob Frege @ 2005-03-03 9:32 UTC (permalink / raw) To: pthreads-win32 I'm concerned about the Sleep(0) in pthread_once: if (!once_control->done) { if (InterlockedIncrement (&(once_control->started)) == 0) { /* * First thread to increment the started variable */ (*init_routine) (); once_control->done = PTW32_TRUE; } else { /* * Block until other thread finishes executing the onceRoutine */ while (!(once_control->done)) { /* * The following gives up CPU cycles without pausing * unnecessarily */ Sleep (0); } } } IIRC, Sleep(0) does not relinquish time slices to lower priority threads. (Sleep(n) for n != 0 does, but 0 does not). So, if a lower priority thread is first in, followed closely by a higher priority one, the higher priority thread will spin on Sleep(0) *forever* because the lower, first thread will never get a chance to set done. So even Sleep(10) should be good enough. In theory, there could be enough higher priority threads in the system that the first thread still doesn't get in (ever?!), but unlikely. And that would probably mean a general design flaw of the calling code, not pthread_once. ? ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: starvation in pthread_once? 2005-03-03 9:32 starvation in pthread_once? Gottlob Frege @ 2005-03-04 23:18 ` Ross Johnson 2005-03-08 2:22 ` Ross Johnson 0 siblings, 1 reply; 15+ messages in thread From: Ross Johnson @ 2005-03-04 23:18 UTC (permalink / raw) To: Pthreads-Win32 list On Thu, 2005-03-03 at 04:32 -0500, Gottlob Frege wrote: > I'm concerned about the Sleep(0) in pthread_once: > Thanks. It looks like this routine needs to be redesigned. Regards. Ross > if (!once_control->done) > { > if (InterlockedIncrement (&(once_control->started)) == 0) > { > /* > * First thread to increment the started variable > */ > (*init_routine) (); > once_control->done = PTW32_TRUE; > > } > else > { > /* > * Block until other thread finishes executing the onceRoutine > */ > while (!(once_control->done)) > { > /* > * The following gives up CPU cycles without pausing > * unnecessarily > */ > Sleep (0); > } > } > } > > IIRC, Sleep(0) does not relinquish time slices to lower priority > threads. (Sleep(n) for n != 0 does, but 0 does not). So, if a lower > priority thread is first in, followed closely by a higher priority > one, the higher priority thread will spin on Sleep(0) *forever* > because the lower, first thread will never get a chance to set done. > > So even Sleep(10) should be good enough. In theory, there could be > enough higher priority threads in the system that the first thread > still doesn't get in (ever?!), but unlikely. And that would probably > mean a general design flaw of the calling code, not pthread_once. > > ? > ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: starvation in pthread_once? 2005-03-04 23:18 ` Ross Johnson @ 2005-03-08 2:22 ` Ross Johnson 2005-03-08 3:00 ` Gottlob Frege 0 siblings, 1 reply; 15+ messages in thread From: Ross Johnson @ 2005-03-08 2:22 UTC (permalink / raw) To: Pthreads-Win32 list All, I've redesigned pthread_once() and will shortly drop it into CVS and a new snapshot. Please take a look over the code below and let me know of any problems or improvements you find. The rationale is explained in the comments. This new version passes a new test I've added to the test suite that exercises the routine much more intensively than previously (although it doesn't play around with thread priorities yet). Thanks. Ross /* * Use a single global cond+mutex to manage access to all once_control objects. * Unlike a global mutex on it's own, the global cond+mutex allows faster * once_controls to overtake slower ones. Spurious wakeups can occur, but * can be tolerated. * * To maintain a separate mutex for each once_control object requires either * cleaning up the mutex (difficult to synchronise reliably), or leaving it * around forever. Since we can't make assumptions about how an application might * employ once_t objects, the later is considered to be unacceptable. * * Since this is being introduced as a bug fix, the global cond+mtx also avoids * a change in the ABI, maintaining backwards compatibility. * * The mutex should be an ERRORCHECK type to be sure we will never, in the event * we're cancelled before we get the lock, unlock the mutex when it's held by * another thread (possible with NORMAL/DEFAULT mutexes because they don't check * ownership). */ if (!once_control->done) { if (InterlockedExchange((LPLONG) &once_control->started, (LONG) 0) == -1) { (*init_routine) (); /* * Holding the mutex during the broadcast prevents threads being left * behind waiting. */ pthread_cleanup_push(pthread_mutex_unlock, (void *) &ptw32_once_control.mtx); (void) pthread_mutex_lock(&ptw32_once_control.mtx); once_control->done = PTW32_TRUE; (void) pthread_cond_broadcast(&ptw32_once_control.cond); pthread_cleanup_pop(1); } else { pthread_cleanup_push(pthread_mutex_unlock, (void *) &ptw32_once_control.mtx); (void) pthread_mutex_lock(&ptw32_once_control.mtx); while (!once_control->done) { (void) pthread_cond_wait(&ptw32_once_control.cond, &ptw32_once_control.mtx); } pthread_cleanup_pop(1); } } On Sat, 2005-03-05 at 10:18 +1100, Ross Johnson wrote: > On Thu, 2005-03-03 at 04:32 -0500, Gottlob Frege wrote: > > I'm concerned about the Sleep(0) in pthread_once: > > > > Thanks. It looks like this routine needs to be redesigned. > > Regards. > Ross > > > if (!once_control->done) > > { > > if (InterlockedIncrement (&(once_control->started)) == 0) > > { > > /* > > * First thread to increment the started variable > > */ > > (*init_routine) (); > > once_control->done = PTW32_TRUE; > > > > } > > else > > { > > /* > > * Block until other thread finishes executing the onceRoutine > > */ > > while (!(once_control->done)) > > { > > /* > > * The following gives up CPU cycles without pausing > > * unnecessarily > > */ > > Sleep (0); > > } > > } > > } > > > > IIRC, Sleep(0) does not relinquish time slices to lower priority > > threads. (Sleep(n) for n != 0 does, but 0 does not). So, if a lower > > priority thread is first in, followed closely by a higher priority > > one, the higher priority thread will spin on Sleep(0) *forever* > > because the lower, first thread will never get a chance to set done. > > > > So even Sleep(10) should be good enough. In theory, there could be > > enough higher priority threads in the system that the first thread > > still doesn't get in (ever?!), but unlikely. And that would probably > > mean a general design flaw of the calling code, not pthread_once. > > > > ? > > ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: starvation in pthread_once? 2005-03-08 2:22 ` Ross Johnson @ 2005-03-08 3:00 ` Gottlob Frege 2005-03-08 6:11 ` Ross Johnson 2005-03-08 16:05 ` Gottlob Frege 0 siblings, 2 replies; 15+ messages in thread From: Gottlob Frege @ 2005-03-08 3:00 UTC (permalink / raw) Cc: Pthreads-Win32 list I was thinking of something like this: (haven't compiled it yet or anything...) if (flag.initted) // quick and easy check { return; } if (!AtomicExchange(&flag.entry, true)) // no one was in before us? { if (func) { func(); } AtomicExchange(&initted, true); // needs release type memory barrier // we didn't create the event. // it is only there if there is someone waiting if (AtomicRead(&flag.event)) { SetEvent(flag.event); } } else { // wait for init. // while waiting, create an event to wait on AtomicIncrement(&flag.eventUsers); // mark that we have committed to creating an event if (!AtomicRead(&flag.event)) { tmpEvent = CreateEvent(true, false); // manual reset (ie will never be reset) if (AtomicCompareExchange(&flag.event, 0, tmpEvent) == 0) // release memory barrier { // wasn't set, is now } else { // someone snuck in // get rid of our attempt CloseHandle(tmpEvent); } } // check initted again in case the initting thread has finished // and left before seeing that there was an event to trigger. // (Now that the event IS created, if init gets finished AFTER this, // then the event handle is guaranteed to be seen and triggered). // // Note also, that our first 'quick and easy' init check did NOT use atomic read; // this atomic read here ensures that initted is properly seen by the CPU cache. // And once seen as 'true' it will always stay true, so no need to worry about the cache anymore. // (Thus the first 'quick and easy' check might 'falsely' fail, but it gets corrected here. if (!AtomicRead(&flag.initted)) // needs acquire type memory barrier { WaitEvent(flag.event); // acquire type memory barrier } // last one out shut off the lights: if (AtomicDecrement(&flag.eventUsers) == 0) // we were last { HANDLE tmpEvent = AtomicExchange(&flag.event, (HANDLE)-1); if (tmpEvent && tmpEvent != (HANDLE)-1); { CloseHandle(tmpEvent); } } } On Tue, 08 Mar 2005 13:22:01 +1100, Ross Johnson <ross.johnson@homemail.com.au> wrote: > All, > > I've redesigned pthread_once() and will shortly drop it into CVS and a > new snapshot. Please take a look over the code below and let me know of > any problems or improvements you find. The rationale is explained in the > comments. > > This new version passes a new test I've added to the test suite that > exercises the routine much more intensively than previously (although it > doesn't play around with thread priorities yet). > > Thanks. > Ross > > /* > * Use a single global cond+mutex to manage access to all once_control objects. > * Unlike a global mutex on it's own, the global cond+mutex allows faster > * once_controls to overtake slower ones. Spurious wakeups can occur, but > * can be tolerated. > * > * To maintain a separate mutex for each once_control object requires either > * cleaning up the mutex (difficult to synchronise reliably), or leaving it > * around forever. Since we can't make assumptions about how an application might > * employ once_t objects, the later is considered to be unacceptable. > * > * Since this is being introduced as a bug fix, the global cond+mtx also avoids > * a change in the ABI, maintaining backwards compatibility. > * > * The mutex should be an ERRORCHECK type to be sure we will never, in the event > * we're cancelled before we get the lock, unlock the mutex when it's held by > * another thread (possible with NORMAL/DEFAULT mutexes because they don't check > * ownership). > */ > > if (!once_control->done) > { > if (InterlockedExchange((LPLONG) &once_control->started, (LONG) 0) == -1) > { > (*init_routine) (); > > /* > * Holding the mutex during the broadcast prevents threads being left > * behind waiting. > */ > pthread_cleanup_push(pthread_mutex_unlock, (void *) &ptw32_once_control.mtx); > (void) pthread_mutex_lock(&ptw32_once_control.mtx); > once_control->done = PTW32_TRUE; > (void) pthread_cond_broadcast(&ptw32_once_control.cond); > pthread_cleanup_pop(1); > } > else > { > pthread_cleanup_push(pthread_mutex_unlock, (void *) &ptw32_once_control.mtx); > (void) pthread_mutex_lock(&ptw32_once_control.mtx); > while (!once_control->done) > { > (void) pthread_cond_wait(&ptw32_once_control.cond, &ptw32_once_control.mtx); > } > pthread_cleanup_pop(1); > } > } > > On Sat, 2005-03-05 at 10:18 +1100, Ross Johnson wrote: > > On Thu, 2005-03-03 at 04:32 -0500, Gottlob Frege wrote: > > > I'm concerned about the Sleep(0) in pthread_once: > > > > > > > Thanks. It looks like this routine needs to be redesigned. > > > > Regards. > > Ross > > > > > if (!once_control->done) > > > { > > > if (InterlockedIncrement (&(once_control->started)) == 0) > > > { > > > /* > > > * First thread to increment the started variable > > > */ > > > (*init_routine) (); > > > once_control->done = PTW32_TRUE; > > > > > > } > > > else > > > { > > > /* > > > * Block until other thread finishes executing the onceRoutine > > > */ > > > while (!(once_control->done)) > > > { > > > /* > > > * The following gives up CPU cycles without pausing > > > * unnecessarily > > > */ > > > Sleep (0); > > > } > > > } > > > } > > > > > > IIRC, Sleep(0) does not relinquish time slices to lower priority > > > threads. (Sleep(n) for n != 0 does, but 0 does not). So, if a lower > > > priority thread is first in, followed closely by a higher priority > > > one, the higher priority thread will spin on Sleep(0) *forever* > > > because the lower, first thread will never get a chance to set done. > > > > > > So even Sleep(10) should be good enough. In theory, there could be > > > enough higher priority threads in the system that the first thread > > > still doesn't get in (ever?!), but unlikely. And that would probably > > > mean a general design flaw of the calling code, not pthread_once. > > > > > > ? > > > > > ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: starvation in pthread_once? 2005-03-08 3:00 ` Gottlob Frege @ 2005-03-08 6:11 ` Ross Johnson 2005-03-08 9:49 ` Alexander Terekhov 2005-03-08 16:05 ` Gottlob Frege 1 sibling, 1 reply; 15+ messages in thread From: Ross Johnson @ 2005-03-08 6:11 UTC (permalink / raw) To: Pthreads-Win32 list On Mon, 2005-03-07 at 22:00 -0500, Gottlob Frege wrote: > I was thinking of something like this: (haven't compiled it yet or anything...) > This is definitely the more interesting solution. A slight downside is the ABI change (pthread_once_t and PTHREAD_ONCE_INIT). So this is what I'd like to do:- - because of the ABI change, I will put out a new snapshot today containing the version I posted earlier (no ABI change and fixes the starvation problem) plus the other bug fix (handle leak fix). - after that, include the version below in CVS after testing it, ready for the next snapshot, which will have a new dll name (pthreadVC2.dll etc.) and compatibility version number. - generate another [new] snapshot in a few days if no complications or other issues arise in the meantime. One other thing:- since pthread_once() is being redesigned, I'm also looking at the latest SUS3 spec and trying to decide what the following paragraph means for threads that are already waiting:- "The pthread_once() function is not a cancelation point. However, if init_routine is a cancelation point and is canceled, the effect on once_control shall be as if pthread_once() was never called." Should one or more waiting threads get woken up and made to re-compete to run the init routine? That's the only sensible (maybe even obvious) thing to do AFAIKS. Ross > if (flag.initted) // quick and easy check > { > return; > } > > if (!AtomicExchange(&flag.entry, true)) // no one was in before us? > { > if (func) > { > func(); > } > > AtomicExchange(&initted, true); // needs release type memory barrier > > // we didn't create the event. > // it is only there if there is someone waiting > if (AtomicRead(&flag.event)) > { > SetEvent(flag.event); > } > } > else > { > // wait for init. > // while waiting, create an event to wait on > > AtomicIncrement(&flag.eventUsers); // mark that we have committed to > creating an event > > if (!AtomicRead(&flag.event)) > { > tmpEvent = CreateEvent(true, false); // manual reset (ie will never be reset) > if (AtomicCompareExchange(&flag.event, 0, tmpEvent) == 0) // > release memory barrier > { > // wasn't set, is now > } > else > { > // someone snuck in > // get rid of our attempt > CloseHandle(tmpEvent); > } > } > > // check initted again in case the initting thread has finished > // and left before seeing that there was an event to trigger. > // (Now that the event IS created, if init gets finished AFTER this, > // then the event handle is guaranteed to be seen and triggered). > // > // Note also, that our first 'quick and easy' init check did NOT use > atomic read; > // this atomic read here ensures that initted is properly seen by > the CPU cache. > // And once seen as 'true' it will always stay true, so no need to > worry about the cache anymore. > // (Thus the first 'quick and easy' check might 'falsely' fail, but > it gets corrected here. > > if (!AtomicRead(&flag.initted)) // needs acquire type memory barrier > { > WaitEvent(flag.event); // acquire type memory barrier > } > > // last one out shut off the lights: > if (AtomicDecrement(&flag.eventUsers) == 0) // we were last > { > HANDLE tmpEvent = AtomicExchange(&flag.event, (HANDLE)-1); > if (tmpEvent && tmpEvent != (HANDLE)-1); > { > CloseHandle(tmpEvent); > } > } > } > > > > On Tue, 08 Mar 2005 13:22:01 +1100, Ross Johnson > <ross.johnson@homemail.com.au> wrote: > > All, > > > > I've redesigned pthread_once() and will shortly drop it into CVS and a > > new snapshot. Please take a look over the code below and let me know of > > any problems or improvements you find. The rationale is explained in the > > comments. > > > > This new version passes a new test I've added to the test suite that > > exercises the routine much more intensively than previously (although it > > doesn't play around with thread priorities yet). > > > > Thanks. > > Ross > > > > /* > > * Use a single global cond+mutex to manage access to all once_control objects. > > * Unlike a global mutex on it's own, the global cond+mutex allows faster > > * once_controls to overtake slower ones. Spurious wakeups can occur, but > > * can be tolerated. > > * > > * To maintain a separate mutex for each once_control object requires either > > * cleaning up the mutex (difficult to synchronise reliably), or leaving it > > * around forever. Since we can't make assumptions about how an application might > > * employ once_t objects, the later is considered to be unacceptable. > > * > > * Since this is being introduced as a bug fix, the global cond+mtx also avoids > > * a change in the ABI, maintaining backwards compatibility. > > * > > * The mutex should be an ERRORCHECK type to be sure we will never, in the event > > * we're cancelled before we get the lock, unlock the mutex when it's held by > > * another thread (possible with NORMAL/DEFAULT mutexes because they don't check > > * ownership). > > */ > > > > if (!once_control->done) > > { > > if (InterlockedExchange((LPLONG) &once_control->started, (LONG) 0) == -1) > > { > > (*init_routine) (); > > > > /* > > * Holding the mutex during the broadcast prevents threads being left > > * behind waiting. > > */ > > pthread_cleanup_push(pthread_mutex_unlock, (void *) &ptw32_once_control.mtx); > > (void) pthread_mutex_lock(&ptw32_once_control.mtx); > > once_control->done = PTW32_TRUE; > > (void) pthread_cond_broadcast(&ptw32_once_control.cond); > > pthread_cleanup_pop(1); > > } > > else > > { > > pthread_cleanup_push(pthread_mutex_unlock, (void *) &ptw32_once_control.mtx); > > (void) pthread_mutex_lock(&ptw32_once_control.mtx); > > while (!once_control->done) > > { > > (void) pthread_cond_wait(&ptw32_once_control.cond, &ptw32_once_control.mtx); > > } > > pthread_cleanup_pop(1); > > } > > } > > > > On Sat, 2005-03-05 at 10:18 +1100, Ross Johnson wrote: > > > On Thu, 2005-03-03 at 04:32 -0500, Gottlob Frege wrote: > > > > I'm concerned about the Sleep(0) in pthread_once: > > > > > > > > > > Thanks. It looks like this routine needs to be redesigned. > > > > > > Regards. > > > Ross > > > > > > > if (!once_control->done) > > > > { > > > > if (InterlockedIncrement (&(once_control->started)) == 0) > > > > { > > > > /* > > > > * First thread to increment the started variable > > > > */ > > > > (*init_routine) (); > > > > once_control->done = PTW32_TRUE; > > > > > > > > } > > > > else > > > > { > > > > /* > > > > * Block until other thread finishes executing the onceRoutine > > > > */ > > > > while (!(once_control->done)) > > > > { > > > > /* > > > > * The following gives up CPU cycles without pausing > > > > * unnecessarily > > > > */ > > > > Sleep (0); > > > > } > > > > } > > > > } > > > > > > > > IIRC, Sleep(0) does not relinquish time slices to lower priority > > > > threads. (Sleep(n) for n != 0 does, but 0 does not). So, if a lower > > > > priority thread is first in, followed closely by a higher priority > > > > one, the higher priority thread will spin on Sleep(0) *forever* > > > > because the lower, first thread will never get a chance to set done. > > > > > > > > So even Sleep(10) should be good enough. In theory, there could be > > > > enough higher priority threads in the system that the first thread > > > > still doesn't get in (ever?!), but unlikely. And that would probably > > > > mean a general design flaw of the calling code, not pthread_once. > > > > > > > > ? > > > > > > > > > ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: starvation in pthread_once? 2005-03-08 6:11 ` Ross Johnson @ 2005-03-08 9:49 ` Alexander Terekhov 2005-03-08 9:56 ` Alexander Terekhov 0 siblings, 1 reply; 15+ messages in thread From: Alexander Terekhov @ 2005-03-08 9:49 UTC (permalink / raw) To: Ross Johnson; +Cc: Pthreads-Win32 list [... pthread_once() and cancelation ...] > Should one or more waiting threads get woken up and made to > re-compete to run the init routine? Yes. As for the rest, what you need here is DCSI-TLS or DCSI-MBR (acquire/release memory barriers are needed on both Itanic and XBOX NEXT... and, apart from hardware memory model, compiler shall respect acquire/release semantics on IA-32 as well). For serialization simply use a named mutex associated with address of control variable and process id (to minimize contention). DCSI-TLS: (__declspec(thread) for control variable; DLL issues aside for a moment) if (!once_control) { named_mutex::guard guard(&once_control); if (!once_control) { <init> once_control = true; } } DCSI-MBR: (atomic<> for control variable) if (!once_control.load(msync::acq)) { named_mutex::guard guard(&once_control); if (!once_control.load(msync::none)) { <init> once_control.store(true, msync::rel); } } regards, alexander. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: starvation in pthread_once? 2005-03-08 9:49 ` Alexander Terekhov @ 2005-03-08 9:56 ` Alexander Terekhov 2005-03-08 9:58 ` Alexander Terekhov 0 siblings, 1 reply; 15+ messages in thread From: Alexander Terekhov @ 2005-03-08 9:56 UTC (permalink / raw) To: Ross Johnson; +Cc: Pthreads-Win32 list > DCSI-TLS: (__declspec(thread) for control variable; DLL issues > aside for a moment) > > if (!once_control) { > named_mutex::guard guard(&once_control); > if (!once_control) { > <init> > once_control = true; > } > } Sorry, I meant: if (!once_control) { named_mutex::guard guard(&once_control); if (!once_control2) { <init> once_control2 = true; } once_control = true; } where once_control2 is a non-TLS flag. regards, alexander. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: starvation in pthread_once? 2005-03-08 9:56 ` Alexander Terekhov @ 2005-03-08 9:58 ` Alexander Terekhov 2005-03-08 16:11 ` Gottlob Frege 0 siblings, 1 reply; 15+ messages in thread From: Alexander Terekhov @ 2005-03-08 9:58 UTC (permalink / raw) To: Ross Johnson; +Cc: Pthreads-Win32 list Grrr. Bad day. if (!once_control) { named_mutex::guard guard(&once_control2); if (!once_control2) { <init> once_control2 = true; } once_control = true; } regards, alexander. Alexander Terekhov/Germany/IBM@IBMDE@sources.redhat.com on 03/08/2005 10:55:53 AM Sent by: pthreads-win32-owner@sources.redhat.com To: Ross Johnson <rpj@callisto.canberra.edu.au> cc: Pthreads-Win32 list <pthreads-win32@sources.redhat.com> Subject: Re: starvation in pthread_once? > DCSI-TLS: (__declspec(thread) for control variable; DLL issues > aside for a moment) > > if (!once_control) { > named_mutex::guard guard(&once_control); > if (!once_control) { > <init> > once_control = true; > } > } Sorry, I meant: if (!once_control) { named_mutex::guard guard(&once_control); if (!once_control2) { <init> once_control2 = true; } once_control = true; } where once_control2 is a non-TLS flag. regards, alexander. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: starvation in pthread_once? 2005-03-08 9:58 ` Alexander Terekhov @ 2005-03-08 16:11 ` Gottlob Frege 2005-03-08 17:14 ` Alexander Terekhov 2005-03-14 2:47 ` Ross Johnson 0 siblings, 2 replies; 15+ messages in thread From: Gottlob Frege @ 2005-03-08 16:11 UTC (permalink / raw) To: Pthreads-Win32 list Do you think it is worth avoiding the named_mutex in favor of a event only created on contention (my version)? (I do, obviously). I'm guessing that for typical usage, contention will be low. And, in my own code, I'm considering making statically initialized mutexes (or CSes) by using a call_once in their constructor. It would just seem like overkill if all my mutexes needed a mutex to init themselves. On Tue, 8 Mar 2005 10:58:23 +0100, Alexander Terekhov <TEREKHOV@de.ibm.com> wrote: > > Grrr. Bad day. > > if (!once_control) { > named_mutex::guard guard(&once_control2); > if (!once_control2) { > <init> > once_control2 = true; > } > once_control = true; > } > > regards, > alexander. > > Alexander Terekhov/Germany/IBM@IBMDE@sources.redhat.com on 03/08/2005 > 10:55:53 AM > > Sent by: pthreads-win32-owner@sources.redhat.com > > To: Ross Johnson <rpj@callisto.canberra.edu.au> > cc: Pthreads-Win32 list <pthreads-win32@sources.redhat.com> > Subject: Re: starvation in pthread_once? > > > > DCSI-TLS: (__declspec(thread) for control variable; DLL issues > > aside for a moment) > > > > if (!once_control) { > > named_mutex::guard guard(&once_control); > > if (!once_control) { > > <init> > > once_control = true; > > } > > } > > Sorry, I meant: > > if (!once_control) { > named_mutex::guard guard(&once_control); > if (!once_control2) { > <init> > once_control2 = true; > } > once_control = true; > } > > where once_control2 is a non-TLS flag. > > regards, > alexander. > > ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: starvation in pthread_once? 2005-03-08 16:11 ` Gottlob Frege @ 2005-03-08 17:14 ` Alexander Terekhov 2005-03-08 18:28 ` Gottlob Frege 2005-03-14 2:47 ` Ross Johnson 1 sibling, 1 reply; 15+ messages in thread From: Alexander Terekhov @ 2005-03-08 17:14 UTC (permalink / raw) To: Gottlob Frege; +Cc: Pthreads-Win32 list > It would just seem like overkill if all my mutexes needed a > mutex to init themselves. With CAS (InterlockedCompareExchange), you can use lockless DCCI (not DCSI) on slow path to initialize event handle/ptr. http://www.google.de/groups?selm=421F44B2.F931491D%40web.de regards, alexander. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: starvation in pthread_once? 2005-03-08 17:14 ` Alexander Terekhov @ 2005-03-08 18:28 ` Gottlob Frege 0 siblings, 0 replies; 15+ messages in thread From: Gottlob Frege @ 2005-03-08 18:28 UTC (permalink / raw) To: Alexander Terekhov; +Cc: Pthreads-Win32 list On Tue, 8 Mar 2005 18:14:22 +0100, Alexander Terekhov <TEREKHOV@de.ibm.com> wrote: > > > It would just seem like overkill if all my mutexes needed a > > mutex to init themselves. > > With CAS (InterlockedCompareExchange), you can use lockless > DCCI (not DCSI) on slow path to initialize event handle/ptr. > > http://www.google.de/groups?selm=421F44B2.F931491D%40web.de > > regards, > alexander. > Yes, I remember that conversion. I've improved my stuff because of it no doubt. But in those examples, the slow path was still taken by the first thread (IIRC - it was because your lock starts out == 0 ie contention.) Whereas my version (above, in this thread) avoids the event creation entirely if there is no contention while initting (ie first one in does the init, but doesn't create event - only triggers the event if it finds one there when initting is done). I'd like to hear your opinion - yours in particular, actually (or wrath - particularly wrath if necessary. If there is a problem I need someone to tell me - threading code is not reliable because of testing, but because of peer review). ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: starvation in pthread_once? 2005-03-08 16:11 ` Gottlob Frege 2005-03-08 17:14 ` Alexander Terekhov @ 2005-03-14 2:47 ` Ross Johnson [not found] ` <97ffb310503140832401faa2b@mail.gmail.com> 1 sibling, 1 reply; 15+ messages in thread From: Ross Johnson @ 2005-03-14 2:47 UTC (permalink / raw) To: Pthreads-Win32 list On Tue, 2005-03-08 at 11:11 -0500, Gottlob Frege wrote: > Do you think it is worth avoiding the named_mutex in favor of a event > only created on contention (my version)? (I do, obviously). > I agree. Win32 mutexes are VERY slow, even for uncontended lock operations - that is, about 50 times slower than a CriticalSection. I would guess that a named mutex is even slower again. A CS is about the same speed as an interlocked operation if it doesn't block. I also think that generating the [guaranteed unique] name is a complication that should be avoided if possible. News: I've dropped a new version of pthread_once into CVS (bumping the library's version from 1 to 2, and dll's name to indicate an ABI change). It's based on the code that Gottlob posted earlier:- http://sources.redhat.com/ml/pthreads-win32/2005/msg00029.html This code works fine without cancellation, but with it included, the design's efficiency had to be downgraded a little. Threads waiting on a cancelled initter thread must be woken to compete again to run the init_routine. I couldn't find any way to manage the repeated creation/reuse/closure of the event properly without a global CS, but at least these sections are very short where it matters most. See:- http://sources.redhat.com/cgi-bin/cvsweb.cgi/pthreads/pthread_once.c? rev=1.10&content-type=text/x-cvsweb-markup&cvsroot=pthreads-win32 Crucially, the event is still a per once_control object that is released on successful return from pthread_once. I plan on releasing one more pthread*1.dll snapshot to include init_routine cancellability based on the global cond+mutex used in the last snapshot, then a pthread*2.dll snapshot using the above code. (DLL names change only when their ABI compatibility is affected). Thanks. Ross > I'm guessing that for typical usage, contention will be low. And, in > my own code, I'm considering making statically initialized mutexes (or > CSes) by using a call_once in their constructor. It would just seem > like overkill if all my mutexes needed a mutex to init themselves. > > > On Tue, 8 Mar 2005 10:58:23 +0100, Alexander Terekhov > <TEREKHOV@de.ibm.com> wrote: > > > > Grrr. Bad day. > > > > if (!once_control) { > > named_mutex::guard guard(&once_control2); > > if (!once_control2) { > > <init> > > once_control2 = true; > > } > > once_control = true; > > } > > > > regards, > > alexander. > > > > Alexander Terekhov/Germany/IBM@IBMDE@sources.redhat.com on 03/08/2005 > > 10:55:53 AM > > > > Sent by: pthreads-win32-owner@sources.redhat.com > > > > To: Ross Johnson <rpj@callisto.canberra.edu.au> > > cc: Pthreads-Win32 list <pthreads-win32@sources.redhat.com> > > Subject: Re: starvation in pthread_once? > > > > > > > DCSI-TLS: (__declspec(thread) for control variable; DLL issues > > > aside for a moment) > > > > > > if (!once_control) { > > > named_mutex::guard guard(&once_control); > > > if (!once_control) { > > > <init> > > > once_control = true; > > > } > > > } > > > > Sorry, I meant: > > > > if (!once_control) { > > named_mutex::guard guard(&once_control); > > if (!once_control2) { > > <init> > > once_control2 = true; > > } > > once_control = true; > > } > > > > where once_control2 is a non-TLS flag. > > > > regards, > > alexander. > > > > > ^ permalink raw reply [flat|nested] 15+ messages in thread
[parent not found: <97ffb310503140832401faa2b@mail.gmail.com>]
[parent not found: <1110842168.21321.78.camel@desk.home>]
[parent not found: <97ffb3105031415473a3ee169@mail.gmail.com>]
[parent not found: <1110855601.21321.203.camel@desk.home>]
[parent not found: <97ffb31050321080747aa5a7c@mail.gmail.com>]
[parent not found: <1111464847.8363.91.camel@desk.home>]
[parent not found: <97ffb3105032209269b0f44e@mail.gmail.com>]
* Re: starvation in pthread_once? [not found] ` <97ffb3105032209269b0f44e@mail.gmail.com> @ 2009-09-16 15:38 ` Gottlob Frege 2009-09-17 1:28 ` Ross Johnson 0 siblings, 1 reply; 15+ messages in thread From: Gottlob Frege @ 2009-09-16 15:38 UTC (permalink / raw) To: Ross Johnson, pthreads-win32 Blast from the past - whatever happened to changing call_once() to not create the named mutex when it wasn't needed? On Tue, Mar 22, 2005 at 1:26 PM, Gottlob Frege <gottlobfrege@gmail.com> wrote: > If it comes down to it, I might vote for daisy-chaining over > busy-looping (assuming the busy-looping is endless). Remember, this > all started because the original implementation was polling/sleeping > on 'initted' - and if the busy-looping thread is high-priority, then > we are locked forever... > > > On Tue, 22 Mar 2005 15:14:07 +1100, Ross Johnson > <rpj@callisto.canberra.edu.au> wrote: >> On Mon, 2005-03-21 at 11:07 -0500, Gottlob Frege wrote: >> >> > So, it doesn't seem to be getting any easier! *Almost* to the point >> > where a big named mutex becomes tempting - there is a lot to be said >> > for simplicity. However, my/the goal is still to at least minimize >> > the non-contention simple init case... >> >> I'm less and less tempted to use a named mutex. Perhaps there's a >> standard technique, but AFAICS it's impossible to guarantee that the >> name is unique across the system (and all Windows variants). >> >> And I agree, minimum overhead for the uncontended case is the top >> priority (after correct behaviour). I'm not concerned at all about speed >> in the cancellation case. >> >> > And the event is still an auto-reset, although I no longer think it >> > really matters - I really haven't had the tenacity to think this stuff >> > through. If it doesn't matter, manual-reset would be better, I think >> > - I don't like having 1 thread relying on another thread waking it up, >> > - for cases where the thread is killed, or strange thread priorities, >> > etc. >> >> It all looks to me like it will work. I don't recall, in the version >> that's in pthreads-win32 now, why I included eventUsers (++/--) in what >> you have as the __lock() sections. Maybe to save additional Atomic calls >> (bus locks). But now I realise [in that version - not yours] that waking >> threads can block unnecessarily when leaving the wait section. >> >> It probably doesn't matter if cancel_event is auto or manual. I think >> there will be at most one thread waiting on it. And, for 'event', like >> you I'm uncomfortable with daisy-chaining SetEvent() calls. >> >> The only problem with the alternative of using a manual-reset event is >> that some thread/s may busy-loop for a bit until an explicit reset >> occurs. It seems untidy, but it's probably more robust than daisy- >> chained SetEvents given the issues you've identified above. >> >> So I'm tempted to leave both events as manual-reset events. I'm also >> guessing that this busy-looping will be extremely rare - perhaps only >> when a new thread sneaks in to become initter, then suspends just inside >> while the first waiter is waking and heading back to the loop start. >> >> I'll run your design and let you know the results. >> >> Thanks. >> Ross >> >> > ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: starvation in pthread_once? 2009-09-16 15:38 ` Gottlob Frege @ 2009-09-17 1:28 ` Ross Johnson 0 siblings, 0 replies; 15+ messages in thread From: Ross Johnson @ 2009-09-17 1:28 UTC (permalink / raw) To: Gottlob Frege; +Cc: pthreads-win32 Hi Gottlob, Vladimir Kliatchko reimplemented pthread_once to use his implementation of MCS (Mellor-Crummey/Scott) locks. From ptw32_MCS_lock.c:- * About MCS locks: * * MCS locks are queue-based locks, where the queue nodes are local to the * thread. The 'lock' is nothing more than a global pointer that points to * the last node in the queue, or is NULL if the queue is empty. * * Originally designed for use as spin locks requiring no kernel resources * for synchronisation or blocking, the implementation below has adapted * the MCS spin lock for use as a general mutex that will suspend threads * when there is lock contention. * * Because the queue nodes are thread-local, most of the memory read/write * operations required to add or remove nodes from the queue do not trigger * cache-coherence updates. * * Like 'named' mutexes, MCS locks consume system resources transiently - * they are able to acquire and free resources automatically - but MCS * locks do not require any unique 'name' to identify the lock to all * threads using it. Gottlob Frege wrote: > Blast from the past - whatever happened to changing call_once() to not > create the named mutex when it wasn't needed? > > > On Tue, Mar 22, 2005 at 1:26 PM, Gottlob Frege <gottlobfrege@gmail.com> wrote: > >> If it comes down to it, I might vote for daisy-chaining over >> busy-looping (assuming the busy-looping is endless). Remember, this >> all started because the original implementation was polling/sleeping >> on 'initted' - and if the busy-looping thread is high-priority, then >> we are locked forever... >> >> >> On Tue, 22 Mar 2005 15:14:07 +1100, Ross Johnson >> <rpj@callisto.canberra.edu.au> wrote: >> >>> On Mon, 2005-03-21 at 11:07 -0500, Gottlob Frege wrote: >>> >>> >>>> So, it doesn't seem to be getting any easier! *Almost* to the point >>>> where a big named mutex becomes tempting - there is a lot to be said >>>> for simplicity. However, my/the goal is still to at least minimize >>>> the non-contention simple init case... >>>> >>> I'm less and less tempted to use a named mutex. Perhaps there's a >>> standard technique, but AFAICS it's impossible to guarantee that the >>> name is unique across the system (and all Windows variants). >>> >>> And I agree, minimum overhead for the uncontended case is the top >>> priority (after correct behaviour). I'm not concerned at all about speed >>> in the cancellation case. >>> >>> >>>> And the event is still an auto-reset, although I no longer think it >>>> really matters - I really haven't had the tenacity to think this stuff >>>> through. If it doesn't matter, manual-reset would be better, I think >>>> - I don't like having 1 thread relying on another thread waking it up, >>>> - for cases where the thread is killed, or strange thread priorities, >>>> etc. >>>> >>> It all looks to me like it will work. I don't recall, in the version >>> that's in pthreads-win32 now, why I included eventUsers (++/--) in what >>> you have as the __lock() sections. Maybe to save additional Atomic calls >>> (bus locks). But now I realise [in that version - not yours] that waking >>> threads can block unnecessarily when leaving the wait section. >>> >>> It probably doesn't matter if cancel_event is auto or manual. I think >>> there will be at most one thread waiting on it. And, for 'event', like >>> you I'm uncomfortable with daisy-chaining SetEvent() calls. >>> >>> The only problem with the alternative of using a manual-reset event is >>> that some thread/s may busy-loop for a bit until an explicit reset >>> occurs. It seems untidy, but it's probably more robust than daisy- >>> chained SetEvents given the issues you've identified above. >>> >>> So I'm tempted to leave both events as manual-reset events. I'm also >>> guessing that this busy-looping will be extremely rare - perhaps only >>> when a new thread sneaks in to become initter, then suspends just inside >>> while the first waiter is waking and heading back to the loop start. >>> >>> I'll run your design and let you know the results. >>> >>> Thanks. >>> Ross >>> >>> >>> ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: starvation in pthread_once? 2005-03-08 3:00 ` Gottlob Frege 2005-03-08 6:11 ` Ross Johnson @ 2005-03-08 16:05 ` Gottlob Frege 1 sibling, 0 replies; 15+ messages in thread From: Gottlob Frege @ 2005-03-08 16:05 UTC (permalink / raw) To: Pthreads-Win32 list By the way, I was thinking that the first flag.initted check didn't need to be atomic because an inaccurate read of false isn't a problem (it is not - it gets handled later). But an out of order read of true would mean that the data set by the init func() might not really be visible yet. So you need if (AtomicRead(flag.initted). Or TLS like Alexander shows. Under Windows, a __declspec(thread) is pretty fast. On Mon, 7 Mar 2005 22:00:14 -0500, Gottlob Frege <gottlobfrege@gmail.com> wrote: > I was thinking of something like this: (haven't compiled it yet or anything...) > > if (flag.initted) // quick and easy check > { > return; > } > > if (!AtomicExchange(&flag.entry, true)) // no one was in before us? > { > if (func) > { > func(); > } > > AtomicExchange(&initted, true); // needs release type memory barrier > > // we didn't create the event. > // it is only there if there is someone waiting > if (AtomicRead(&flag.event)) > { > SetEvent(flag.event); > } > } > else > { > // wait for init. > // while waiting, create an event to wait on > > AtomicIncrement(&flag.eventUsers); // mark that we have committed to > creating an event > > if (!AtomicRead(&flag.event)) > { > tmpEvent = CreateEvent(true, false); // manual reset (ie will never be reset) > if (AtomicCompareExchange(&flag.event, 0, tmpEvent) == 0) // > release memory barrier > { > // wasn't set, is now > } > else > { > // someone snuck in > // get rid of our attempt > CloseHandle(tmpEvent); > } > } > > // check initted again in case the initting thread has finished > // and left before seeing that there was an event to trigger. > // (Now that the event IS created, if init gets finished AFTER this, > // then the event handle is guaranteed to be seen and triggered). > // > // Note also, that our first 'quick and easy' init check did NOT use > atomic read; > // this atomic read here ensures that initted is properly seen by > the CPU cache. > // And once seen as 'true' it will always stay true, so no need to > worry about the cache anymore. > // (Thus the first 'quick and easy' check might 'falsely' fail, but > it gets corrected here. > > if (!AtomicRead(&flag.initted)) // needs acquire type memory barrier > { > WaitEvent(flag.event); // acquire type memory barrier > } > > // last one out shut off the lights: > if (AtomicDecrement(&flag.eventUsers) == 0) // we were last > { > HANDLE tmpEvent = AtomicExchange(&flag.event, (HANDLE)-1); > if (tmpEvent && tmpEvent != (HANDLE)-1); > { > CloseHandle(tmpEvent); > } > } > } > > On Tue, 08 Mar 2005 13:22:01 +1100, Ross Johnson > <ross.johnson@homemail.com.au> wrote: > > All, > > > > I've redesigned pthread_once() and will shortly drop it into CVS and a > > new snapshot. Please take a look over the code below and let me know of > > any problems or improvements you find. The rationale is explained in the > > comments. > > > > This new version passes a new test I've added to the test suite that > > exercises the routine much more intensively than previously (although it > > doesn't play around with thread priorities yet). > > > > Thanks. > > Ross > > > > /* > > * Use a single global cond+mutex to manage access to all once_control objects. > > * Unlike a global mutex on it's own, the global cond+mutex allows faster > > * once_controls to overtake slower ones. Spurious wakeups can occur, but > > * can be tolerated. > > * > > * To maintain a separate mutex for each once_control object requires either > > * cleaning up the mutex (difficult to synchronise reliably), or leaving it > > * around forever. Since we can't make assumptions about how an application might > > * employ once_t objects, the later is considered to be unacceptable. > > * > > * Since this is being introduced as a bug fix, the global cond+mtx also avoids > > * a change in the ABI, maintaining backwards compatibility. > > * > > * The mutex should be an ERRORCHECK type to be sure we will never, in the event > > * we're cancelled before we get the lock, unlock the mutex when it's held by > > * another thread (possible with NORMAL/DEFAULT mutexes because they don't check > > * ownership). > > */ > > > > if (!once_control->done) > > { > > if (InterlockedExchange((LPLONG) &once_control->started, (LONG) 0) == -1) > > { > > (*init_routine) (); > > > > /* > > * Holding the mutex during the broadcast prevents threads being left > > * behind waiting. > > */ > > pthread_cleanup_push(pthread_mutex_unlock, (void *) &ptw32_once_control.mtx); > > (void) pthread_mutex_lock(&ptw32_once_control.mtx); > > once_control->done = PTW32_TRUE; > > (void) pthread_cond_broadcast(&ptw32_once_control.cond); > > pthread_cleanup_pop(1); > > } > > else > > { > > pthread_cleanup_push(pthread_mutex_unlock, (void *) &ptw32_once_control.mtx); > > (void) pthread_mutex_lock(&ptw32_once_control.mtx); > > while (!once_control->done) > > { > > (void) pthread_cond_wait(&ptw32_once_control.cond, &ptw32_once_control.mtx); > > } > > pthread_cleanup_pop(1); > > } > > } > > > > On Sat, 2005-03-05 at 10:18 +1100, Ross Johnson wrote: > > > On Thu, 2005-03-03 at 04:32 -0500, Gottlob Frege wrote: > > > > I'm concerned about the Sleep(0) in pthread_once: > > > > > > > > > > Thanks. It looks like this routine needs to be redesigned. > > > > > > Regards. > > > Ross > > > > > > > if (!once_control->done) > > > > { > > > > if (InterlockedIncrement (&(once_control->started)) == 0) > > > > { > > > > /* > > > > * First thread to increment the started variable > > > > */ > > > > (*init_routine) (); > > > > once_control->done = PTW32_TRUE; > > > > > > > > } > > > > else > > > > { > > > > /* > > > > * Block until other thread finishes executing the onceRoutine > > > > */ > > > > while (!(once_control->done)) > > > > { > > > > /* > > > > * The following gives up CPU cycles without pausing > > > > * unnecessarily > > > > */ > > > > Sleep (0); > > > > } > > > > } > > > > } > > > > > > > > IIRC, Sleep(0) does not relinquish time slices to lower priority > > > > threads. (Sleep(n) for n != 0 does, but 0 does not). So, if a lower > > > > priority thread is first in, followed closely by a higher priority > > > > one, the higher priority thread will spin on Sleep(0) *forever* > > > > because the lower, first thread will never get a chance to set done. > > > > > > > > So even Sleep(10) should be good enough. In theory, there could be > > > > enough higher priority threads in the system that the first thread > > > > still doesn't get in (ever?!), but unlikely. And that would probably > > > > mean a general design flaw of the calling code, not pthread_once. > > > > > > > > ? > > > > > > > > > ^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2009-09-17 1:28 UTC | newest] Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2005-03-03 9:32 starvation in pthread_once? Gottlob Frege 2005-03-04 23:18 ` Ross Johnson 2005-03-08 2:22 ` Ross Johnson 2005-03-08 3:00 ` Gottlob Frege 2005-03-08 6:11 ` Ross Johnson 2005-03-08 9:49 ` Alexander Terekhov 2005-03-08 9:56 ` Alexander Terekhov 2005-03-08 9:58 ` Alexander Terekhov 2005-03-08 16:11 ` Gottlob Frege 2005-03-08 17:14 ` Alexander Terekhov 2005-03-08 18:28 ` Gottlob Frege 2005-03-14 2:47 ` Ross Johnson [not found] ` <97ffb310503140832401faa2b@mail.gmail.com> [not found] ` <1110842168.21321.78.camel@desk.home> [not found] ` <97ffb3105031415473a3ee169@mail.gmail.com> [not found] ` <1110855601.21321.203.camel@desk.home> [not found] ` <97ffb31050321080747aa5a7c@mail.gmail.com> [not found] ` <1111464847.8363.91.camel@desk.home> [not found] ` <97ffb3105032209269b0f44e@mail.gmail.com> 2009-09-16 15:38 ` Gottlob Frege 2009-09-17 1:28 ` Ross Johnson 2005-03-08 16:05 ` Gottlob Frege
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).