Win32 Programming Lesson 11: User-mode Thread Sync (aka: How to crash your machine without really...

Preview:

Citation preview

Win32 ProgrammingLesson 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 re

source 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_SE

CTION 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_SE

CTION pcs);  Can use:

BOOL TryEnterCriticalSection(PCRITICAL_SECTION pcs);

Don’t wait: return TRUE/FALSE

Leaving VOID LeaveCriticalSection(PCRITICAL_SE

CTION 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

Recommended