From 58de21f1e7cde4c32bfd03b545ac3ede6b6a8e38 Mon Sep 17 00:00:00 2001 From: rpj Date: Thu, 26 May 2005 08:15:53 +0000 Subject: '' --- ChangeLog | 10 +- GNUmakefile | 16 ++-- README | 14 +++ pthread.h | 39 +------- pthread_once.c | 285 ++++++++++++++------------------------------------------- 5 files changed, 107 insertions(+), 257 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2c4ef6d..537b365 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,12 @@ -2005-05-24 Mikael Magnusson +2005-05-25 Vladimir Kliatchko + + * 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 * 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 diff --git a/README b/README index 091fa89..5649333 100644 --- a/README +++ b/README @@ -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 ----------------------------------------------------- diff --git a/pthread.h b/pthread.h index 64f21b9..93060eb 100644 --- a/pthread.h +++ b/pthread.h @@ -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 -#endif - #ifndef NEED_FTIME #include #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 */ -- cgit v1.2.3