Windows Concurrency Waiting and Synchronization Copyright © 1997 – 2016 Curt Hill Introduction • We have previously seen the presentations on threads • We now need to look at the wait functions and other synchronization APIs • First we consider race errors – You should have previously seen this • Then how to combat these – With synchronization objects – With waits Copyright © 1997 – 2016 Curt Hill Race Example • Consider two tasks that share an integer variable share • Task A has – share = share + 2; • Task B has – share = share + 5; Copyright © 1997 – 2016 Curt Hill Racing Form C and Assembly • • • • ;share += 2 mov ax,share add ax,2 mov share,ax • • • • ;share += 5 mov ax,share add ax,5 mov share,ax There are three possible outcomes Copyright © 1997 – 2016 Curt Hill Race Results • If one task completes before the other starts share will have 7 added to it – Complete serialization • If A starts and is interrupted, then B executes, then the add of two is kept and the add of five is lost • If B starts first and is interrupted, then the add of five is kept and the add of two is lost Copyright © 1997 – 2016 Curt Hill Classical Problems • There are a number of problems that illustrate race errors • The first is the producer and consumer problem • One task fills a buffer and another empties it • This problem is inside every file copy • Consider the Producer and Consumer problem: Copyright © 1997 – 2016 Curt Hill Producer and Consumer Problem Remove point Add point Length Copyright © 1997 – 2016 Curt Hill Producer/Consumer • A buffer exists char buf[80]; int len,strt,end; • A thread adds a char: if(len<80){ end=(end+1)%80; buf[end] = inchar; len=len+1; } • A thread empties: if(len>0) { strt = (strt+1)%80; outchar = buf[strt]; len=len-1; } Copyright © 1997 – 2016 Curt Hill Producers and Consumers • The critical thing here is the len variable • What happens if they both try to store to that variable at the same time? • The assignment statement is not a single machine language statement in all cases • Caching only complicates things in a multi CPU system Copyright © 1997 – 2016 Curt Hill Recursion • The C family all allow recursion • This is because variables are by default automatic – Created when a function is entered and discarded upon exit • We are not particularly interested in recursion today but it leads us to a type of function that we are interested in – Reentrant Copyright © 1997 – 2016 Curt Hill Reentrant Function • A reentrant function allows multiple threads in it while preserving thread-safety • Every function in C/C++ is reentrant if it has these characteristics – Only uses parameters and local variables – No reference/pointer parameters refer to shared variables – No variables are static – It only calls reentrant functions Copyright © 1997 – 2016 Curt Hill More • A reentrant function does not need synchronization – Thus we prefer to call these – they do not slow the threads • It is only using things that are on the local thread’s stack – There is no sharing • Every reentrant function is thread safe but not every thread safe function is reentrant Copyright © 1997 – 2016 Curt Hill Functions and methods • Many functions are reentrant just because they do not share anything – Good function design prefers use of value parameters and local variables • Most methods are not – They access instance variables or static variables Copyright © 1997 – 2016 Curt Hill Serialization • Any time there is sharing we must be able to serialize threads – Prevent two threads from executing code in the same area – What we need is to detect that another thread has control and wait the new thread – These are available in the hardware • A race condition exists if we do not serialize • This leads us to the topic of synchronization Copyright © 1997 – 2016 Curt Hill Synchronization • Having threads executing without any communication is questionable in most cases • Synchronization provides the ability to serialize our threads so that no race errors occur • Windows, like any OS, provides several synchronization methods Copyright © 1997 – 2016 Curt Hill • Synchronization Items There are several that are interesting – Critical section – Semaphores – Mutex • We may not need to consider all of them – We will anyway • These are not OO classes but instead structs and standalone methods – Makes it easier to err Copyright © 1997 – 2016 Curt Hill One More • Recall that Windows was built with C and not C++ – It has structs and no classes • Another synchronization item is called a monitor • It is a class that encapsulates a critical section • Thus only one thread can be active in any of its methods Copyright © 1997 – 2016 Curt Hill Critical Section • A critical section guards one or more portions of code • Only one thread may be executing in any of these pieces at a time • If the thread is suspended while inside it still possesses the code – Calling subsequent function from within also leaves the thread owning the code • These are used in the threader demo Copyright © 1997 – 2016 Curt Hill Critical Section • This is the struct: CRITICAL_SECTION – Usually addressed by a long pointer • Create with new but initialize with InitializeCriticalSection – Parameter is the pointer • The functions are: – EnterCriticalSection – LeaveCriticalSection – TryEnterCriticalSection returns TRUE if it was obtained – All take the pointer Copyright © 1997 – 2016 Curt Hill Signatures • void WINAPI InitializeCriticalSection( _Out_ LPCRITICAL_SECTION lpCriticalSection ); • void WINAPI EnterCriticalSection( _Inout_ LPCRITICAL_SECTION lpCriticalSection ); • void WINAPI LeaveCriticalSection( _Inout_ LPCRITICAL_SECTION lpCriticalSection ); • BOOL WINAPI TryEnterCriticalSection( _Inout_ LPCRITICAL_SECTION lpCriticalSection ); Copyright © 1997 – 2016 Curt Hill On Your Honor • The four functions and the corresponding control block know nothing about any code • Instead they believe that you put an Enter and Leave around code that needs to be serialized • This code may in same class or anywhere in the process – As long as they all have access to the pointer to the critical section Copyright © 1997 – 2016 Curt Hill Critical Section Guidelines • Keep your critical sections short – Long ones reduce concurrency • The process should usually provide memory for the struct – We do not want a thread to end and destroy the struct • Multiple critical sections may be used to protect multiple shared variables • One critical section may protect any number of references with the enters/leaves Copyright © 1997 – 2016 Curt Hill Semaphores • Named after the flags that pre-radio ships used to communicate • Similar to critical sections and mutexs in many ways • A semaphore has an arbitrary maximum value of threads that it may allow into a portion of code – This is always one for a critical section • Only use these if it is acceptable for multiple threads to share code simultaneously Copyright © 1997 – 2016 Curt Hill Signatures • HANDLE WINAPI CreateSemaphore( _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, _In_ LONG lInitialCount, _In_ LONG lMaximumCount, _In_opt_ LPCTSTR lpName ); • HANDLE WINAPI OpenSemaphore( _In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ LPCTSTR lpName ); • BOOL WINAPI ReleaseSemaphore( _In_ HANDLE hSemaphore, _In_ LONG lReleaseCount, _Out_opt_ LPLONG lpPreviousCount ); Copyright © 1997 – 2016 Curt Hill Usage • The main process will create the semaphore – It specifies the maximum count and the current count • An Open checks the count – If there is more capacity it increments the count – Otherwise it waits until a thread leaves • Release decrements the count – It releases the semaphore Copyright © 1997 – 2016 Curt Hill Waiting • Waiting is something that most threads do – They wait for another task to complete something • Waiting can be of two forms: • Busy waiting – AKA polling, spinning on a loop – Wastes CPU time - undesirable • One of several OS wait calls – Removes the thread from the ready queue until an event happens Copyright © 1997 – 2016 Curt Hill OS Waiting • When waiting we must always wait for an event to happen • This event may be one of several things – A timer fires – An external action occurs – A process/thread completes some action – A combination of several of these • More on these a little later Copyright © 1997 – 2016 Curt Hill Sleep • Suspends the thread for a number of milliseconds • Sets a timer – Uses the timer event to resume • Signature: VOID WINAPI Sleep( _In_ DWORD dwMilliseconds); • May be used in a good polling routine where there is nothing that passes for an event available • Also frequently used in animation Copyright © 1997 – 2016 Curt Hill … Sleep Example while (OK) { if(var != oldvar) { process_var(var); oldvar = var; } Sleep(20); } Changing a variable, by itself, cannot get us out of a wait. We sleep 20 ms. each time through the loop. This is polling, but the sleep keeps it from being wasteful. Leaving sleep out makes it wasteful. Copyright © 1997 – 2016 Curt Hill WaitForObject • There are two that are used – Single and multiple • Signatures: DWORD WINAPI WaitForSingleObject( _In_ HANDLE hHandle, _In_ DWORD dwMilliseconds ); DWORD WINAPIWaitForMultipleObjectsEx( _In_ DWORD nCount, _In_ const HANDLE *lpHandles, _In_ BOOL bWaitAll, _In_ DWORD dwMilliseconds, _In_ BOOL bAlertable ); Copyright © 1997 – 2016 Curt Hill WaitForSingleObject • The thread is suspended until this function returns – This is not a busy wait • Two parameters – An object handle – consider soon – Timeout value in milliseconds • Returns a DWORD which says how the wait was terminated – See next screen Copyright © 1997 – 2016 Curt Hill Wait Returns • There are several possible returns: • WAIT_OBJECT_0 – The object signaled • WAIT_TIMEOUT – The timer fired without the object signaling • WAIT_ABANDONED – The thread owning the object terminated before the object signaled • WAIT_FAILED – No waiting occurred – use GetLastError for this one Copyright © 1997 – 2016 Curt Hill WaitForMultipleObjects • Similar to single except we pass an array of objects – And a count to say how many • There is also a Boolean that indicates to wait for any one of these or all of them – AND or OR them • Its return also introduces other WAIT_OBJECTs and WAIT_ABANDONEDs – WAIT_OBJECT_1, WAIT_OBJECT_2 … Copyright © 1997 – 2016 Curt Hill Wait Objects • What events may a WaitForObject function consider? – This is not an object in class sense • A partial list includes: – – – – – Event Mutex Process Semaphore Thread • Do you think more explanation is needed? Copyright © 1997 – 2016 Curt Hill Wait objects Again • The wait objects do not have to be of the same type in WaitForMultipleObjects – There could be a mix of different types • When either a process or thread ends this constitutes an event – Thus we may use the handle of either a process or thread in the wait Copyright © 1997 – 2016 Curt Hill Summary on Wait • Waits are best used when one of the synchronization APIs cannot be used – Exception is mutex, which requires the waits • Generally it is better to use a synchronization API Copyright © 1997 – 2016 Curt Hill Event • An event is a control structure with one of two states – Signaled or not signaled • We may then do a wait on an event until it is signaled • The functions involved are – CreateEvent – SignalEvent – ResetEvent Copyright © 1997 – 2016 Curt Hill Create Event • The CreateEvent returns a handle • It takes four parameters: – LPSECURITY_ATTRIBUTES – may be NULL – BOOL – TRUE requires manual reset – BOOL – TRUE means it starts signaled – LPCTSTR – Name, which may be NULL • The returned event may be used in a Wait • Handles other than Events may be waited for as well Copyright © 1997 – 2016 Curt Hill Manual and Automatic • The second parameter is whether the event needs a Reset • A TRUE means that the user has to manually do the Reset when a thread is released from a Wait • A FALSE means that only one thread is released from a Wait and then it is reset Copyright © 1997 – 2016 Curt Hill Setting Events • SetEvent(handle) makes the event to be in the signaled state • A ResetEvent(handle) makes the event to be not signaled – This is not usually needed with an automatic event Copyright © 1997 – 2016 Curt Hill Mutexs • A Mutex is similar to a critical section – Somewhat different usage • Unlike a critical section it may be used by multiple processes – Much less needed but very valuable when it is • The steps are to: – Create or Open – Release – Close the handle Copyright © 1997 – 2016 Curt Hill Signatures HANDLE WINAPI CreateMutex( _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, _In_ BOOL bInitialOwner, _In_opt_ LPCTSTR lpName ); HANDLE WINAPI OpenMutex( _In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ LPCTSTR lpName ); BOOL WINAPI ReleaseMutex( _In_ HANDLE hMutex ); Copyright © 1997 – 2016 Curt Hill Ownership • A mutex may be owned by only one thread on the system at a time • The owner is not the creator – Mutex may be created as owned or not owned • The ReleaseMutex releases ownership • OpenMutex gets a handle for a named mutex that some other process created Copyright © 1997 – 2016 Curt Hill Usage • One process does the CreateMutex – It retains the handle – If the owner parameter is false then it is not owned by the thread • A thread obtains the ownership for the mutex by using a Wait for Single or Multiple Object – It will wait until the mutex is not owned – Then it will own the mutex until it releases it Copyright © 1997 – 2016 Curt Hill Conclusion • Multiple threads and synchronization are tricky • This presentation may or may not have given you enough information to do • There is one more and then some demonstration programs – Spawning threads Copyright © 1997 – 2016 Curt Hill
© Copyright 2025 Paperzz