// Shave and a Haircut // (c) 2019 Epic Games // US Patent 6720962 #ifdef _WIN32 #include #include #else #include #include #endif #include "shaveSDKFUNCS.h" typedef struct { enum { kWaiting, kWorkAvailable, kRunning, kExit } state; unsigned index; ThreadFunc func; void *data; ShaveMutex *mutex; ShaveCond *completionCond; ShaveCond stateCond; #ifdef _WIN32 HANDLE id; #else pthread_t id; #endif } ThreadInfo; typedef struct { ShaveMutex mutex; int numThreads; ThreadInfo *threads; ShaveCond completionCond; } ThreadGroup; #ifdef _WIN32 static unsigned __stdcall threadProc( void *data ) #else static void * threadProc( void *data ) #endif { ThreadInfo *info = ( ThreadInfo * ) data; SHAVEmutex_lock( info->mutex ); while( info->state != kExit ) { while( info->state == kWaiting ) SHAVEcond_wait( &info->stateCond, info->mutex ); if( info->state == kWorkAvailable ) { info->state = kRunning; SHAVEmutex_unlock( info->mutex ); ( info->func ) ( info->index, info->data ); SHAVEmutex_lock( info->mutex ); info->state = kWaiting; SHAVEcond_signal( info->completionCond ); } } SHAVEmutex_unlock( info->mutex ); return 0; } void * SHAVEstart_thread_group( int maxThreads ) { // // Create the thread group. // int i; int st; ThreadGroup *group = ( ThreadGroup * ) malloc( sizeof( ThreadGroup ) ); if( group ) { if( maxThreads <= 0 ) { group->numThreads = 0; group->threads = NULL; } else { group->numThreads = maxThreads; group->threads = ( ThreadInfo * ) malloc( sizeof( ThreadInfo ) * maxThreads ); if( !group->threads ) { free( group ); group = NULL; } else { #ifdef _WIN32 unsigned threadID; #endif SHAVEmutex_create( &group->mutex ); SHAVEcond_create( &group->completionCond ); // // Create the individual threads within the group. // for( i = 0; i < maxThreads; i++ ) { group->threads[i].index = ( unsigned ) i; group->threads[i].state = kWaiting; group->threads[i].mutex = &group->mutex; group->threads[i].completionCond = &group->completionCond; SHAVEcond_create( &group->threads[i].stateCond ); #ifdef _WIN32 group->threads[i].id = ( HANDLE ) _beginthreadex( NULL, 0, threadProc, &group->threads[i], 0, &threadID ); st = ( group->threads[i].id == 0 ); #else st = pthread_create( &group->threads[i].id, NULL, threadProc, &group->threads[i] ); #endif if( st != 0 ) { if( i > 0 ) { group->numThreads = i; SHAVEend_thread_group( group ); } group = NULL; break; } } } } } return group; } void SHAVEstart_thread( void *groupHdl, ThreadFunc func, void *data ) { ThreadGroup *group = ( ThreadGroup * ) groupHdl; if( group->numThreads == 0 ) func( 0, data ); else { int i; int threadStarted = 0; SHAVEmutex_lock( &group->mutex ); while( !threadStarted ) { // // Loop through each of the threads, looking for one which is ready // to do work. // for( i = 0; i < group->numThreads; i++ ) { if( group->threads[i].state == kWaiting ) { group->threads[i].func = func; group->threads[i].data = data; group->threads[i].state = kWorkAvailable; SHAVEcond_signal( &group->threads[i].stateCond ); threadStarted = 1; break; } } // // If none of the threads was ready for work, then wait for one of // them to signal that it's done with its current task. // if( !threadStarted ) SHAVEcond_wait( &group->completionCond, &group->mutex ); } SHAVEmutex_unlock( &group->mutex ); } } void SHAVEwait_thread_group( void *groupHdl ) { ThreadGroup *group = ( ThreadGroup * ) groupHdl; if( group->numThreads > 0 ) { int i; SHAVEmutex_lock( &group->mutex ); for( i = 0; i < group->numThreads; i++ ) { // // Wait for the thread to finish whatever it's doing. // while( group->threads[i].state != kWaiting ) SHAVEcond_wait( &group->completionCond, &group->mutex ); } SHAVEmutex_unlock( &group->mutex ); } } void SHAVEend_thread_group( void *groupHdl ) { ThreadGroup *group = ( ThreadGroup * ) groupHdl; if( group->numThreads > 0 ) { int i; SHAVEmutex_lock( &group->mutex ); for( i = 0; i < group->numThreads; i++ ) { // // Wait for the thread to finish whatever it's doing. // while( group->threads[i].state != kWaiting ) SHAVEcond_wait( &group->completionCond, &group->mutex ); // // Tell the thread to exit. // group->threads[i].state = kExit; SHAVEcond_signal( &group->threads[i].stateCond ); // // Wait for it to finish exiting. // SHAVEmutex_unlock( &group->mutex ); #ifdef _WIN32 WaitForSingleObject( group->threads[i].id, INFINITE ); CloseHandle( group->threads[i].id ); #else pthread_join( group->threads[i].id, NULL ); #endif SHAVEmutex_lock( &group->mutex ); SHAVEcond_destroy( &group->threads[i].stateCond ); } SHAVEmutex_unlock( &group->mutex ); SHAVEmutex_destroy( &group->mutex ); SHAVEcond_destroy( &group->completionCond ); free( group->threads ); } free( group ); } void SHAVEabort_thread_group( void *groupHdl ) { #ifdef _WIN32 // // Windows does not provide any way of aborting a thread, so we must // wait for all the threads to finish. // SHAVEend_thread_group( groupHdl ); #else ThreadGroup *group = ( ThreadGroup * ) groupHdl; if( group->numThreads > 0 ) { int i; for( i = 0; i < group->numThreads; i++ ) { // // Cancel the thread. // pthread_cancel( group->threads[i].id ); // // Wait for it to finish exiting. // pthread_join( group->threads[i].id, NULL ); SHAVEcond_destroy( &group->threads[i].stateCond ); } SHAVEcond_destroy( &group->completionCond ); SHAVEmutex_destroy( &group->mutex ); free( group->threads ); } free( group ); #endif } int SHAVEnum_processors( ) { #if defined(linux) int n = sysconf( _SC_NPROCESSORS_ONLN ); if( n < 1 ) n = 1; return n; #elif defined(OSMac_) return MPProcessors( ); #elif defined(_WIN32) SYSTEM_INFO info; GetSystemInfo( &info ); return info.dwNumberOfProcessors; #endif return 1; } void SHAVEset_max_threads( int n ) { if( n > SHAVE_MAX_THREADS ) GmaxThreads = SHAVE_MAX_THREADS; else GmaxThreads = n; } // // Initialize a mutex. // void SHAVEmutex_create( ShaveMutex * mutex ) { #ifndef NOLIB #ifdef _WIN32 *mutex = CreateMutex( NULL, FALSE, NULL ); #else pthread_mutex_init( mutex, NULL ); #endif #endif } // // Free up any resources used by a mutex. // void SHAVEmutex_destroy( ShaveMutex * mutex ) { #ifndef NOLIB #ifdef _WIN32 CloseHandle( *mutex ); #else pthread_mutex_destroy( mutex ); #endif #endif } // // Lock a mutex. If it's currently locked by someone else, wait // for it to come free first. // void SHAVEmutex_lock( ShaveMutex * mutex ) { #ifndef NOLIB #ifdef _WIN32 WaitForSingleObject( *mutex, INFINITE ); #else pthread_mutex_lock( mutex ); #endif #endif } // // Unlock a mutex. // void SHAVEmutex_unlock( ShaveMutex * mutex ) { #ifndef NOLIB #ifdef _WIN32 ReleaseMutex( *mutex ); #else pthread_mutex_unlock( mutex ); #endif #endif } // // Create a condition variable. // void SHAVEcond_create( ShaveCond * cond ) { #ifndef NOLIB #ifdef _WIN32 *cond = CreateEvent( NULL, FALSE, FALSE, NULL ); #else pthread_cond_init( cond, NULL ); #endif #endif } // // Destroy a condition variable. // void SHAVEcond_destroy( ShaveCond * cond ) { #ifndef NOLIB #ifdef _WIN32 CloseHandle( *cond ); #else pthread_cond_destroy( cond ); #endif #endif } // // Signal the condition. If there are threads waiting on it one (and only // one) will be activated. // void SHAVEcond_signal( ShaveCond * cond ) { #ifndef NOLIB #ifdef _WIN32 SetEvent( *cond ); #else pthread_cond_signal( cond ); #endif #endif } // // Wait for the condition to be signalled. // void SHAVEcond_wait( ShaveCond * cond, ShaveMutex * mutex ) { #ifndef NOLIB #ifdef _WIN32 SignalObjectAndWait( *mutex, *cond, INFINITE, FALSE ); #else pthread_cond_wait( cond, mutex ); #endif #endif }