Concurrency in Windows

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