diff options
| author | rpj <rpj> | 2005-05-26 08:15:53 +0000 | 
|---|---|---|
| committer | rpj <rpj> | 2005-05-26 08:15:53 +0000 | 
| commit | 58de21f1e7cde4c32bfd03b545ac3ede6b6a8e38 (patch) | |
| tree | 395a821800af83ce199eca4cddf30f015ab68042 | |
| parent | 610c2958a3bedd909ccab7971d1e67f6671512a1 (diff) | |
''
| -rw-r--r-- | ChangeLog | 10 | ||||
| -rw-r--r-- | GNUmakefile | 16 | ||||
| -rw-r--r-- | README | 14 | ||||
| -rw-r--r-- | pthread.h | 39 | ||||
| -rw-r--r-- | pthread_once.c | 285 | 
5 files changed, 107 insertions, 257 deletions
| @@ -1,4 +1,12 @@ -2005-05-24  Mikael Magnusson <mikaelmagnusson at glocalnet.net> +2005-05-25  Vladimir Kliatchko  <vladimir at kliatchko.com> + +	* pthread_once.c: Eliminate all priority operations and other +	complexity by replacing the event with a semaphore. The advantage +	of the change is the ability to release just one waiter if the +	init_routine thread is cancelled yet still release all waiters when +	done. + +2005-05-24  Mikael Magnusson  <mikaelmagnusson at glocalnet.net>  	* GNUmakefile: Patched to allow cross-compile with mingw32 on Linux.  	It uses macros instead of referencing dlltool, gcc and g++ directly; diff --git a/GNUmakefile b/GNUmakefile index 716d47e..992251d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -41,18 +41,22 @@ LIBDEST	= $(DEVROOT)\DLL  RM	= rm -f  MV	= mv -f  CP	= cp -f -RC	= windres  # If not.  #RM	= erase  #MV	= rename  #CP	= copy -AR	= ar -DLLTOOL = dlltool -CC      = gcc -CXX     = g++ -RANLIB  = ranlib +# For cross compiling use e.g. +# make CROSS=i386-mingw32msvc- clean GC-inlined +CROSS	=  + +AR	= $(CROSS)ar +DLLTOOL = $(CROSS)dlltool +CC      = $(CROSS)gcc +CXX     = $(CROSS)g++ +RANLIB  = $(CROSS)ranlib +RC	= $(CROSS)windres  OPT	= $(CLEANUP) -O3 -finline-functions  DOPT	= $(CLEANUP) -g -O0 @@ -411,6 +411,20 @@ make clean GCE-bench   (to benchtest using GNU C dll with C++ exception handling  make clean GC-static   (to test using GC static lib with C (no EH) applications) +Building under Linux using the Mingw32 cross development tools +-------------------------------------------------------------- + +You can build the library without leaving Linux by using the Mingw32 cross +development toolchain. See http://www.libsdl.org/extras/win32/cross/ for +tools and info. The GNUmakefile contains some support for this, for example: + +make CROSS=i386-mingw32msvc- clean GC-inlined + +will build pthreadGCn.dll and libpthreadGCn.a (n=version#), provided your +cross-tools/bin directory is in your PATH (or use the cross-make.sh script +at the URL above). + +  Building the library as a statically linkable library  ----------------------------------------------------- @@ -191,15 +191,6 @@  /* Try to avoid including windows.h */  #if defined(__MINGW32__) && defined(__cplusplus) -/* - * FIXME: The pthreadGCE.dll build gets linker unresolved errors - * on pthread_key_create() unless windows.h is included here. - * It appears to have something to do with an argument type mismatch. - * Looking at tsd.o with 'nm' shows this line: - * 00000000 T _pthread_key_create__FPP14pthread_key_t_PFPv_v - * instead of - * 00000000 T _pthread_key_create - */  #define PTW32_INCLUDE_WINDOWS_H  #endif @@ -223,26 +214,6 @@ typedef unsigned long DWORD_PTR;  #include "config.h"  #endif /* HAVE_CONFIG_H */ -#if PTW32_LEVEL >= PTW32_LEVEL_MAX - -/* Try to avoid including windows.h */ -#if defined(__MINGW32__) && defined(__cplusplus) -/* - * FIXME: The pthreadGCE.dll build gets linker unresolved errors - * on pthread_key_create() unless windows.h is included here. - * It appears to have something to do with an argument type mismatch. - * Looking at tsd.o with 'nm' shows this line: - * 00000000 T _pthread_key_create__FPP14pthread_key_t_PFPv_v - * instead of - * 00000000 T _pthread_key_create - */ -#define PTW32_INCLUDE_WINDOWS_H -#endif - -#ifdef PTW32_INCLUDE_WINDOWS_H -#include <windows.h> -#endif -  #ifndef NEED_FTIME  #include <time.h>  #else /* NEED_FTIME */ @@ -332,8 +303,6 @@ enum {  #endif  #endif -#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ -  #ifndef HAVE_STRUCT_TIMESPEC  #define HAVE_STRUCT_TIMESPEC 1  struct timespec { @@ -684,14 +653,14 @@ enum {   * ====================   * ====================   */ -#define PTHREAD_ONCE_INIT       { 0, PTW32_FALSE, 0, 0} +#define PTHREAD_ONCE_INIT       { PTW32_FALSE, PTW32_FALSE, 0, 0}  struct pthread_once_t_  { -  int          state;        /* indicates if user function has been executed, or cancelled  */ +  int          done;        /* indicates if user function has been executed */    int          started; -  int          eventUsers; -  HANDLE       event; +  int          numSemaphoreUsers; +  HANDLE       semaphore;  }; diff --git a/pthread_once.c b/pthread_once.c index 0ee063a..b42036f 100644 --- a/pthread_once.c +++ b/pthread_once.c @@ -96,9 +96,13 @@   * be restricted to the post cancellation tracks. That is, it need not slow   * the normal cancel-free behaviour. Threads remain independent of other threads.   * - * The implementation below adds only a few local (to the thread) integer comparisons - * to the normal track through the routine and additional bus locking/cache line - * syncing operations have been avoided altogether in the uncontended track. + * Version E + * --------- + * Substituting a semaphore in place of the event achieves the same effect as an + * auto-reset event in the post cancellation phase, and a manual-reset event in the + * normal exit phase. The new initter thread does not need to do any post-cancellation + * operations, and waiters only need to check that there is a new initter running + * before starting to wait. All priority issues and adjustments disappear.   */  #include "pthread.h" @@ -110,72 +114,55 @@ ptw32_once_init_routine_cleanup(void * arg)  {    pthread_once_t * once_control = (pthread_once_t *) arg; -  (void) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->state, (LONG)PTW32_ONCE_CANCELLED);    (void) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->started, (LONG)PTW32_FALSE); -  if (InterlockedExchangeAdd((LPLONG)&once_control->event, 0L)) /* MBR fence */ +  if (InterlockedExchangeAdd((LPLONG)&once_control->semaphore, 0L)) /* MBR fence */      { -      int lasterror = GetLastError (); -      int lastWSAerror = WSAGetLastError (); - -      /* -       * There are waiters, wake some up. -       */ -      if (!SetEvent(once_control->event)) -	{ -	  SetLastError (lasterror); -	  WSASetLastError (lastWSAerror); -	} +      ReleaseSemaphore(once_control->semaphore, 1, NULL);      }  } -  int  pthread_once (pthread_once_t * once_control, void (*init_routine) (void)) -	/* -	 * ------------------------------------------------------ -	 * DOCPUBLIC -	 *      If any thread in a process  with  a  once_control  parameter -	 *      makes  a  call to pthread_once(), the first call will summon -	 *      the init_routine(), but  subsequent  calls  will  not. The -	 *      once_control  parameter  determines  whether  the associated -	 *      initialization routine has been called.  The  init_routine() -	 *      is complete upon return of pthread_once(). -	 *      This function guarantees that one and only one thread -	 *      executes the initialization routine, init_routine when -	 *      access is controlled by the pthread_once_t control -	 *      key. -	 * -	 *      pthread_once() is not a cancelation point, but the init_routine -	 *      can be. If it's cancelled then the effect on the once_control is -	 *      as if pthread_once had never been entered. -	 * -	 * -	 * PARAMETERS -	 *      once_control -	 *              pointer to an instance of pthread_once_t -	 * -	 *      init_routine -	 *              pointer to an initialization routine -	 * -	 * -	 * DESCRIPTION -	 *      See above. -	 * -	 * RESULTS -	 *              0               success, -	 *              EINVAL          once_control or init_routine is NULL -	 * -	 * ------------------------------------------------------ -	 */ +     /* +      * ------------------------------------------------------ +      * DOCPUBLIC +      *      If any thread in a process  with  a  once_control  parameter +      *      makes  a  call to pthread_once(), the first call will summon +      *      the init_routine(), but  subsequent  calls  will  not. The +      *      once_control  parameter  determines  whether  the associated +      *      initialization routine has been called.  The  init_routine() +      *      is complete upon return of pthread_once(). +      *      This function guarantees that one and only one thread +      *      executes the initialization routine, init_routine when +      *      access is controlled by the pthread_once_t control +      *      key. +      * +      *      pthread_once() is not a cancelation point, but the init_routine +      *      can be. If it's cancelled then the effect on the once_control is +      *      as if pthread_once had never been entered. +      * +      * +      * PARAMETERS +      *      once_control +      *              pointer to an instance of pthread_once_t +      * +      *      init_routine +      *              pointer to an initialization routine +      * +      * +      * DESCRIPTION +      *      See above. +      * +      * RESULTS +      *              0               success, +      *              EINVAL          once_control or init_routine is NULL +      * +      * ------------------------------------------------------ +      */  {    int result; -  int lasterror; -  int lastWSAerror; -  int restoreLastError; -  LONG state; -  pthread_t self; -  HANDLE w32Thread = 0; +  HANDLE sema;    if (once_control == NULL || init_routine == NULL)      { @@ -187,67 +174,10 @@ pthread_once (pthread_once_t * once_control, void (*init_routine) (void))        result = 0;      } -  /* -   * We want to be invisible to GetLastError() outside of this routine. -   */ -  lasterror = GetLastError (); -  lastWSAerror = WSAGetLastError (); -  restoreLastError = PTW32_FALSE; - -  while (!((state = InterlockedExchangeAdd((LPLONG)&once_control->state, 0L)) /* Atomic Read */ -	   & (LONG)PTW32_ONCE_DONE)) +  while (!InterlockedExchangeAdd((LPLONG)&once_control->done, 0L)) /* Atomic Read */      { -      LONG cancelled = (state & PTW32_ONCE_CANCELLED); - -      if (cancelled) -	{ -	  /* Boost priority momentarily */ -	  if (!w32Thread) -	    { -	      self = pthread_self(); -	      w32Thread = ((ptw32_thread_t *)self.p)->threadH; -	    } -	  /* -	   * Prevent pthread_setschedparam() from changing our priority while we're boosted. -	   */ -	  pthread_mutex_lock(&((ptw32_thread_t *)self.p)->threadLock); -	  SetThreadPriority(w32Thread, THREAD_PRIORITY_HIGHEST); -	} -        if (!PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->started, (LONG)PTW32_TRUE))  	{ -	  if (cancelled) -	    { -	      /* -	       * The previous initter was cancelled. -	       * We now have a new initter (us) and we need to make the rest wait again. -	       * Furthermore, we're running at max priority until after we've reset the event -	       * so we will not be starved by any other threads that may now be looping -	       * around. -	       */ -	      if (InterlockedExchangeAdd((LPLONG)&once_control->event, 0L)) /* MBR fence */ -		{ -		  if (!ResetEvent(once_control->event)) -		    { -		      restoreLastError = PTW32_TRUE; -		    } -		} - -	      /* -	       * Any threads entering the wait section and getting out again before -	       * the event is reset and the CANCELLED state is cleared will, at worst, -	       * just go around again or, if they suspend and we (the initter) completes before -	       * they resume, they will see state == DONE and leave immediately. -	       */ -	      PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->state, (LONG)PTW32_ONCE_CLEAR); - -	      /* -	       * Restore priority. We catch any changes to this thread's priority -	       * only if they were done through the POSIX API (i.e. pthread_setschedparam) -	       */ -	      SetThreadPriority(w32Thread, ((ptw32_thread_t *)self.p)->sched_priority); -	      pthread_mutex_unlock(&((ptw32_thread_t *)self.p)->threadLock); -	    }  #ifdef _MSC_VER  #pragma inline_depth(0) @@ -261,128 +191,54 @@ pthread_once (pthread_once_t * once_control, void (*init_routine) (void))  #pragma inline_depth()  #endif -	  (void) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->state, (LONG)PTW32_ONCE_DONE); +	  (void) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->done, (LONG)PTW32_TRUE);  	  /* -	   * we didn't create the event. +	   * we didn't create the semaphore.  	   * it is only there if there is someone waiting. -	   * Avoid using the global event_lock but still prevent SetEvent -	   * from overwriting any 'lasterror' if the event is closed before we -	   * are done with it.  	   */ -	  if (InterlockedExchangeAdd((LPLONG)&once_control->event, 0L)) /* MBR fence */ +	  if (InterlockedExchangeAdd((LPLONG)&once_control->semaphore, 0L)) /* MBR fence */  	    { -	      if (!SetEvent(once_control->event)) -		{ -		  restoreLastError = PTW32_TRUE; -		} +	      ReleaseSemaphore(once_control->semaphore, once_control->numSemaphoreUsers, NULL);  	    }  	}        else  	{ -	  HANDLE tmpEvent; +	  InterlockedIncrement((LPLONG)&once_control->numSemaphoreUsers); -	  if (cancelled) +	  if (!InterlockedExchangeAdd((LPLONG)&once_control->semaphore, 0L)) /* MBR fence */  	    { -	      /* -	       * Restore priority. We catch any changes to this thread's priority -	       * only if they were done through the POSIX API (i.e. pthread_setschedparam. -	       */ -	      SetThreadPriority(w32Thread, ((ptw32_thread_t *)self.p)->sched_priority); -	      pthread_mutex_unlock(&((ptw32_thread_t *)self.p)->threadLock); -	    } +	      sema = CreateSemaphore(NULL, 0, INT_MAX, NULL); -	  /* -	   * wait for init. -	   * while waiting, create an event to wait on -	   */ - -	  if (1 == InterlockedIncrement((LPLONG)&once_control->eventUsers)) -	    { -	      /* -	       * RE CANCELLATION: -	       * If we are the first thread after the initter thread, and the init_routine is cancelled -	       * while we're suspended at this point in the code:- -	       * - state will not get set to PTW32_ONCE_DONE; -	       * - cleanup will not see an event and cannot set it; -	       * - therefore, we will eventually resume, create an event and wait on it; -	       * cleanup will set state == CANCELLED before checking for an event, so that -	       * we will see it and avoid waiting (as for state == DONE). We will go around again and -	       * we may then become the initter. -	       * If we are still the only other thread when we get to the end of this block, we will -	       * have closed the event (good). If another thread beats us to be initter, then we will -	       * re-enter here (good). In case the old event is reused, the event is always reset by -	       * the new initter before clearing the CANCELLED state, causing any threads that are -	       * cycling around the loop to wait again. -	       * The initter thread is guaranteed to be at equal or higher priority than any waiters -	       * so no waiters will starve the initter, which might otherwise cause us to loop -	       * forever. -	       */ -	      tmpEvent = CreateEvent(NULL, PTW32_TRUE, PTW32_FALSE, NULL); -	      if (PTW32_INTERLOCKED_COMPARE_EXCHANGE((PTW32_INTERLOCKED_LPLONG)&once_control->event, -						     (PTW32_INTERLOCKED_LONG)tmpEvent, +	      if (PTW32_INTERLOCKED_COMPARE_EXCHANGE((PTW32_INTERLOCKED_LPLONG)&once_control->semaphore, +						     (PTW32_INTERLOCKED_LONG)sema,  						     (PTW32_INTERLOCKED_LONG)0))  		{ -		  CloseHandle(tmpEvent); +		  CloseHandle(sema);  		}  	    }  	  /* -	   * Check 'state' again in case the initting thread has finished or cancelled -	   * and left before seeing that there was an event to trigger. +	   * Check 'done' and 'started' again in case the initting thread has finished or cancelled +	   * and left before seeing that there was a semaphore to release.  	   */ - -	  switch (InterlockedExchangeAdd((LPLONG)&once_control->state, 0L)) +	  if (InterlockedExchangeAdd((LPLONG)&once_control->done, 0L) /* Done immediately, or */ +	      || !InterlockedExchangeAdd((LPLONG)&once_control->started, 0L) /* No initter yet, or */ +	      || WaitForSingleObject(once_control->semaphore, INFINITE)) /* Done or Cancelled */  	    { -	    case PTW32_ONCE_CLEAR: -	      { -		/* Neither DONE nor CANCELLED */ -		if (WAIT_FAILED == WaitForSingleObject(once_control->event, INFINITE)) -		  { -		    restoreLastError = PTW32_TRUE; -		    /* -		     * If the wait failed it's probably because the event is invalid. -		     * That's possible after a cancellation (but rare) if we got through the -		     * event create block above while a woken thread was suspended between -		     * the decrement and exchange below and then resumed before we could wait. -		     * So we'll yield. -		     */ -		    Sleep(0); -		  } -		break; -	      } -	    case PTW32_ONCE_CANCELLED: -	      { -		if (once_control->started) -		  { -		    /* The new initter hasn't cleared the cancellation yet, so give the -		     * processor to a more productive thread. */ -		    Sleep(0); -		  } -		break; -	      } -	    } - -	  /* last one out shut off the lights */ -	  if (0 == InterlockedDecrement((LPLONG)&once_control->eventUsers)) -	    { -	      /* we were last */ -	      if ((tmpEvent = (HANDLE) -		   PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->event, -					      (LONG)0))) +	      if (0 == InterlockedDecrement((LPLONG)&once_control->numSemaphoreUsers))  		{ -		  CloseHandle(tmpEvent); +		  /* we were last */ +		  if ((sema = (HANDLE) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->semaphore, +								  (LONG)0))) +		    { +		      CloseHandle(sema); +		    }  		}  	    }  	}      } -  if (restoreLastError) -    { -      SetLastError (lasterror); -      WSASetLastError (lastWSAerror); -    } -    /*     * ------------     * Failure Code @@ -390,5 +246,4 @@ pthread_once (pthread_once_t * once_control, void (*init_routine) (void))     */  FAIL0:    return (result); - -}				/* pthread_once */ +}                                               /* pthread_once */  | 
