SO heres TDM's thread routine used in my project.
/*
===================
CreateThreadStartRoutine
===================
*/
typedef std::pair<xthread_t, void *> CreateThreadStartParams;
DWORD WINAPI CreateThreadStartRoutine( LPVOID lpThreadParameter ) {
std::pair<xthread_t, void *> arg = *( ( CreateThreadStartParams * )lpThreadParameter );
delete ( ( CreateThreadStartParams * )lpThreadParameter );
return arg.first( arg.second );
}
/*
==================
Sys_Createthread
==================
*/
void Sys_CreateThread( xthread_t function, void *parms, xthreadPriority priority, xthreadInfo &info, const char *name, xthreadInfo *threads[MAX_THREADS], int *thread_count ) {
Sys_EnterCriticalSection();
LPVOID threadParam = new CreateThreadStartParams( function, parms );
HANDLE temp = CreateThread( NULL, // LPSECURITY_ATTRIBUTES lpsa,
0, // DWORD cbStack,
CreateThreadStartRoutine, // LPTHREAD_START_ROUTINE lpStartAddr,
threadParam, // LPVOID lpvThreadParm,
0, // DWORD fdwCreate,
&info.threadId );
info.threadHandle = ( intptr_t )temp;
if ( priority == THREAD_HIGHEST ) {
SetThreadPriority( ( HANDLE )info.threadHandle, THREAD_PRIORITY_HIGHEST ); // we better sleep enough to do this
} else if ( priority == THREAD_ABOVE_NORMAL ) {
SetThreadPriority( ( HANDLE )info.threadHandle, THREAD_PRIORITY_ABOVE_NORMAL );
} else {
// if we hit this then the programmer forgot to set a default thread priority.
SetThreadPriority( ( HANDLE )info.threadHandle, GetThreadPriority( ( HANDLE )info.threadHandle ) != THREAD_PRIORITY_ERROR_RETURN );
}
info.name = name;
if ( *thread_count < MAX_THREADS ) {
threads[( *thread_count )++] = &info;
} else {
common->DPrintf( "WARNING: MAX_THREADS reached\n" );
}
Sys_LeaveCriticalSection();
}
And here is the function to kill the threads at exit.
void Sys_DestroyThread( xthreadInfo &info ) {
DWORD dwExitCode, dwWaitResult, dwThreadCount;
HANDLE dwThreadHandle[MAX_THREADS];
// no threads running so nothing to kill.
if ( !info.threadHandle ) {
return;
}
Sys_EnterCriticalSection();
// give it a little time
Sys_Sleep( 1000 );
// get number of threads to wait for.
for ( dwThreadCount = 0; dwThreadCount < MAX_THREADS; dwThreadCount++ ) {
// create an array of handles for WaitForMultipleObjects.
dwThreadHandle[dwThreadCount] = ( HANDLE ) info.threadHandle;
// wait for the handle to be signaled.
dwWaitResult = WaitForMultipleObjects( dwThreadCount, dwThreadHandle, TRUE, INFINITE );
// signal handlers for WaitForMultipleObjects.
switch ( dwWaitResult ) {
case WAIT_ABANDONED_0:
// Major problem somewhere mutex object might have been killed prematurely.
idLib::common->Printf( "Mutex object was not released by the thread that owned the mutex object before the owning thread terminates...\n" );
break;
case WAIT_OBJECT_0:
// The condition we want.
idLib::common->Printf( "The child thread state was signaled!\n" );
break;
case WAIT_TIMEOUT:
// Thread might be busy.
idLib::common->Printf( "Time-out interval elapsed, and the child thread's state is nonsignaled.\n" );
break;
case WAIT_FAILED:
// Fatal this condition would crash us anyway so might as well let it, yeah right...
idLib::common->Printf( "WaitForMultipleObjects() failed, error %u\n", ::GetLastError() );
return; // get the hell outta here!
}
// Get thread exit status and close the handle.
if ( ::GetExitCodeThread( dwThreadHandle, &dwExitCode ) != FALSE ) {
ExitThread( dwExitCode );
if ( CloseHandle( dwThreadHandle ) != FALSE ) {
dwThreadHandle[dwThreadCount] = NULL;
}
}
}
Sys_LeaveCriticalSection();
}
it is called in one place only at the end of Sys_Quit just before ExitProcess(0);
This is done to make sure nothing is still hooked when exiting the thread and only runs at game shutdown.
The entercriticalsection and leavecriticalsection additions are there to make sure the process owns the running thread.
The last function delegates the running threads on multicore machines eg. > 2 cores.
void Sys_SetThreadAffinity( bool mainthread ) {
SYSTEM_INFO info;
// check number of processors
GetSystemInfo( &info );
// single core machine so but out.
if ( info.dwNumberOfProcessors < 2 ) {
return;
}
// set thread affinity for main thread on core 1 or 3
if ( mainthread ) {
switch ( info.dwNumberOfProcessors ) {
case 1:
SetThreadAffinityMask( GetCurrentThread(), ( 1 << info.dwNumberOfProcessors ) );
break;
case 3:
SetThreadAffinityMask( GetCurrentThread(), ( 3 << info.dwNumberOfProcessors ) );
break;
default:
break;
}
} else {
// set affinity for other threads on core 2 or 4
switch ( info.dwNumberOfProcessors ) {
case 2:
SetThreadAffinityMask( ( HANDLE )threadInfo.threadHandle, ( 2 << info.dwNumberOfProcessors ) );
break;
case 4:
SetThreadAffinityMask( ( HANDLE )threadInfo.threadHandle, ( 4 << info.dwNumberOfProcessors ) );
break;
default:
break;
}
}
}
If say we have a quad core it would delegate the main thread on core 1 and 3 and the second thread on core 2 and 4.
Doom3 uses only 2 threads, one runs the game and another for background file reads.
void idFileSystemLocal::StartBackgroundDownloadThread() {
if ( !backgroundThread.threadHandle ) {
Sys_CreateThread( ( xthread_t )BackgroundDownloadThread, NULL, THREAD_NORMAL, backgroundThread, "backgroundDownload", g_threads, &g_thread_count );
if ( !backgroundThread.threadHandle ) {
common->Warning( "idFileSystemLocal::StartBackgroundDownloadThread: failed" );
}
} else {
common->Printf( "background thread already running\n" );
}
// give the async thread an affinity for the 2 or 4'th core.
Sys_SetThreadAffinity();
}
and here
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) {
#ifdef _DEBUG
SetCurrentDirectory( "C:\\Doom\\Doom 3" );
#endif
const HCURSOR hcurSave = ::SetCursor( LoadCursor( 0, IDC_WAIT ) );
// tell windows we're high dpi aware, otherwise display scaling screws up the game
Sys_SetHighDPIMode();
Sys_SetPhysicalWorkMemory( 192 << 20, 1024 << 20 );
Sys_GetCurrentMemoryStatus( exeLaunchMemoryStats );
win32.hInstance = hInstance;
idStr::Copynz( sys_cmdline, lpCmdLine, sizeof( sys_cmdline ) );
// done before Com/Sys_Init since we need this for error output
Sys_CreateConsole();
// no abort/retry/fail errors
SetErrorMode( SEM_FAILCRITICALERRORS );
for ( int i = 0; i < MAX_CRITICAL_SECTIONS; i++ ) {
InitializeCriticalSection( &win32.criticalSections[i] );
}
// get the initial time base
Sys_Milliseconds();
#ifdef DEBUG
// disable the painfully slow MS heap check every 1024 allocs
_CrtSetDbgFlag( 0 );
#endif
Sys_FPU_SetPrecision( FPU_PRECISION_DOUBLE_EXTENDED );
common->Init( 0, NULL, lpCmdLine );
#ifndef ID_DEDICATED
if ( win32.win_notaskkeys.GetInteger() ) {
DisableTaskKeys( TRUE, FALSE, FALSE );
}
#endif
Sys_StartAsyncThread();
// hide or show the early console as necessary
if ( win32.win_viewlog.GetInteger() || com_skipRenderer.GetBool() || idAsyncNetwork::serverDedicated.GetInteger() ) {
Sys_ShowConsole( 1, true );
} else {
Sys_ShowConsole( 0, false );
}
// give the main thread an affinity for the first or 3'rd core.
Sys_SetThreadAffinity( true );
::SetCursor( hcurSave );
::SetFocus( win32.hWnd );
// main game loop
while ( 1 ) {
Win_Frame();
// run the game
common->Frame();
}
// never gets here
return 0;
}
Also need an extern for it in Sys_Public.h
void Sys_SetThreadAffinity( bool mainthread = false );