210 likes | 383 Views
Win32 Programming. Lesson 11: User-mode Thread Sync (aka: How to crash your machine without really trying…). Where are we?. We’ve got thread basics worked out… even priorities But right now, all the examples are sort-of contrived… Need to understand how to communicate from thread to thread.
E N D
Win32 Programming Lesson 11: User-mode Thread Sync (aka: How to crash your machine without really trying…)
Where are we? • We’ve got thread basics worked out… even priorities • But right now, all the examples are sort-of contrived… • Need to understand how to communicate from thread to thread
Thread Problems • When we share data between threads, bad things can happen • Look at the following example code… • What’s wrong? • Can replace using “interlocked” functions
InterlockedExchangeAdd • LONG InterlockedExchangeAdd ( PLONG plAddend, LONG lIncrement); • What could be simpler – promises Atomic access to *plAddend
Family • LONG InterlockedExchange( PLONG plTarget, LONG lValue); • PVOID InterlockedExchangePointer( PVOID* ppvTarget, PVOID pvValue);
Spinlock… • // Global variable indicating whether a shared resource is in use or not BOOL g_fResourceInUse = FALSE; void Func1() { // Wait to access the resource. while (InterlockedExchange ( &g_fResourceInUse, TRUE) == TRUE) Sleep(0); // Access the resource. // We no longer need to access the resource. InterlockedExchange(&g_fResourceInUse, FALSE); }
Spinlocks (cntd) • Be careful on a single processor machine • Is Sleep(0) the best call? Why?
More Interlocked Calls… • PVOID InterlockedCompareExchange( PLONG plDestination, LONG lExchange, LONG lComparand); • PVOID InterlockedCompareExchangePointer( PVOID* ppvDestination, PVOID pvExchange, PVOID pvComparand); • Basically, update on equality…
Cashing in… (groan) • When a CPU accesses memory, it does not read a single byte, but instead fills a “cache line” (32 or 64 bytes, aligned on a 32 or 64 byte boundary) • If the cache becomes dirty it is flushed • Has a huge impact on how to design data structures for speed
_declspec(align32) • See MSDN • Basically, forces alignment on a cache boundary • See: declspec example…
DON’T DO THIS! • volatile BOOL g_fFinishedCalculation = FALSE; int WINAPI WinMain(...) { CreateThread(..., RecalcFunc, ...); // Wait for the recalculation to complete. while (!g_fFinishedCalculation) ; }DWORD WINAPI RecalcFunc(PVOID pvParam) { // Perform the recalculation. g_fFinishedCalculation = TRUE; return(0);}
Volatile? • Well? • Oops (optimizer…): • ; Copy the value into a register Label: MOV Reg0, [g_fFinishedCalculation] TEST Reg0, 0; Is the value 0? JMP Reg0 == 0, Label; The register is 0, try again ... ; The register is not 0 (end of loop)
Critical Section • Can mark a code section as critical • This prevents any other thread accessing the resources within the critical section • Other threads do still get scheduled though…
Look at this… • const int MAX_TIMES = 1000; int g_nIndex = 0; DWORD g_dwTimes[MAX_TIMES]; DWORD WINAPI FirstThread(PVOID pvParam) { while (g_nIndex < MAX_TIMES) { g_dwTimes[g_nIndex] = GetTickCount(); g_nIndex++; } return(0);}DWORD WINAPI SecondThread(PVOID pvParam) { while (g_nIndex < MAX_TIMES) { g_nIndex++; g_dwTimes[g_nIndex - 1] = GetTickCount(); } return(0); }
Fixed.. • const int MAX_TIMES = 1000; int g_nIndex = 0; DWORD g_dwTimes[MAX_TIMES];CRITICAL_SECTION g_cs;DWORD WINAPI FirstThread(PVOID pvParam) { while (g_nIndex < MAX_TIMES) {EnterCriticalSection(&g_cs); g_dwTimes[g_nIndex] = GetTickCount(); g_nIndex++;LeaveCriticalSection(&g_cs); } return(0);}DWORD WINAPI SecondThread(PVOID pvParam) { while (g_nIndex < MAX_TIMES) {EnterCriticalSection(&g_cs); g_nIndex++; g_dwTimes[g_nIndex - 1] = GetTickCount();LeaveCriticalSection(&g_cs); } return(0); }
But… • You MUST remember to leave a critical section else no other thread can use the resource • And the Devil really is in the details
Initialization • Before you can use a CRITICAL SECTION you must initialize it • VOID InitializeCriticalSection(PCRITICAL_SECTION pcs); • Results undefined if you don’t do this… • BTW, what do you notice about the return?
Entering… • If no thread is using the resource, continue • Else put the calling thread into a WAIT state • VOID EnterCriticalSection(PCRITICAL_SECTION pcs); • Can use: • BOOL TryEnterCriticalSection(PCRITICAL_SECTION pcs); • Don’t wait: return TRUE/FALSE
Leaving • VOID LeaveCriticalSection(PCRITICAL_SECTION pcs); • If we’re done with this Section check for any other threads waiting and mark 1 as schedulable • VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);
Tips • Use ONE critical section per shared resource • Access multiple resources using multiple critical sections • Don’t hold on to a critical section for a long time
Next… • Thread Synchronization with Kernel objects