Chapter 26
26.1 THREAD’S
CREATION
2
26.2 THREAD’S
EXAMPLE
4
26.2.1 THREAD
PROCEDURE
4
26.3 SYNCHRONIZATION
5
26.3.1 OVERLAPPED
INPUT AND OUTPUT
5
26.3.2 ASYNCHRONOUS
PROCEDURE
CALL
7
26.3.3 CRITICAL
SECTION
7
26.4 WAIT
FUNCTIONS
8
SINGLE-OBJECT
WAIT
FUNCTIONS
9
MULTIPLE-OBJECT
WAIT
FUNCTIONS
9
ALERTABLE
WAIT
FUNCTIONS
9
REGISTERED
WAIT
FUNCTIONS
10
WAIT
FUNCTIONS AND SYNCHRONIZATION
OBJECTS
10
WAIT
FUNCTIONS AND CREATING
WINDOWS
10
26.5 SYNCHRONIZATION
OBJECTS
11
26.5.1 MUTEX
OBJECT
12
26.6 THREAD
EXAMPLE
USING
MUTEX
OBJECT
14
26.7 CHECKING IF THE PREVIOUS APPLICATION IS RUNNING
14
26.8 EVENT
OBJECT
15
26.8.1 USING
EVENT
OBJECT
(EXAMPLE) 17
26.9 SEMAPHORE
OBJECT
20
26.10 THREAD
LOCAL
STORAGE
(TLS) 22
API IMPLEMENTATION FOR
TLS 22
COMPILER
IMPLEMENTATION
FOR TLS 22
SUMMARY
22
EXERCISES
22
Threads and Synchronization
2
26.1 Thread’s Creation
The CreateThread
function creates a thread to execute within the virtual address space
of the calling process.
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES
lpThreadAttributes,
SIZE_T
dwStackSize,
LPTHREAD_START_ROUTINE
lpStartAddress,
LPVOID
lpParameter,
DWORD
dwCreationFlags,
LPDWORD
lpThreadId
);lpThreadAttributes:
Pointer to a SECURITY_ATTRIBUTES
structure that determines
whether the returned handle can be inherited by child processes. If
lpThreadAttributes
is
NULL, the handle cannot be inherited.
The
lpSecurityDescriptor member of the structure specifies a security
descriptor
for the new thread. If
lpThreadAttributes is NULL, the thread gets a default
security descriptor. The ACLs in the default security descriptor for a
thread come
from the primary or impersonation token of the creator.
dwStackSize: Initial size of the stack, in bytes. The system
rounds this value to the nearest
page. If this parameter is zero, the new thread uses the default size for
the executable.
lpStartAddress: Pointer to the application-defined function to be
executed by the thread
and represents the starting address of the thread.
lpParameter: Pointer to a variable to be passed to the thread.
dwCreationFlags: Flags that control the creation of the thread.
If the
CREATE_SUSPENDED flag is specified, the thread is created in a suspended
state, and
will not run until the
ResumeThread function is called. If this value is zero, the thread
runs immediately after creation.
lpThreadId: Pointer to a variable that receives the thread
identifier. If this parameter is
NULL, the thread identifier is not returned.
Return value: If the function succeeds, the return value is a
handle to the new thread.
If the function fails, the return value is NULL.
The number of threads a process can create is limited by the available
virtual memory. By
default, every thread has one megabyte of stack space. Therefore, you can
create at most
2028 threads. If you reduce the default stack size, you can create more
threads. However,
Threads and Synchronization 3
your application will have better performance if you create one thread per
processor and
build queues of requests for which the application maintains the context
information. A
thread would process all requests in a queue before processing requests in
the next queue.
The new thread handle is created with the THREAD_ALL_ACCESS access right. If
a
security descriptor is not provided, the handle can be used in any function
that requires a
thread object handle. When a security descriptor is provided, an access
check is
performed on all subsequent uses of the handle before access is granted. If
the access
check denies access, the requesting process cannot use the handle to gain
access to the
thread. If the thread impersonates a client, then calls
CreateThread
with a NULL
security descriptor, the thread object created has a default security
descriptor which
allows access only to the impersonation token's TokenDefaultDacl owner or
members.
The thread execution begins at the function specified by the
lpStartAddress
parameter. If
this function returns, the
DWORD
return value is used to terminate the thread in an
implicit call to the
ExitThread function. Use the
GetExitCodeThread
function to get
the thread's return value.
The thread is created with a thread priority of THREAD_PRIORITY_NORMAL. Use
the
GetThreadPriority and
SetThreadPriority
functions to get and set the priority value of
a thread.
When a thread terminates, the thread object attains a signaled state,
satisfying any threads
that were waiting on the object.
The thread object remains in the system until the thread has terminated and
all handles to
it have been closed through a call to
CloseHandle.
The ExitProcess,
ExitThread,
CreateThread,
CreateRemoteThread
functions, and a
process that is starting (as the result of a call by
CreateProcess) are serialized between
each other within a process. Only one of these events can happen in an
address space at a
time. This means that the following restrictions hold:
Do not create a thread while impersonating another user. The call will
succeed, however
the newly created thread will have reduced access rights to itself when
calling
GetCurrentThread. The access rights granted are derived from
the access rights that the
impersonated user has to the process. Some access rights including
THREAD_SET_THREAD_TOKEN and THREAD_GET_CONTEXT may not be
present, leading to unexpected failures.
• During process startup and DLL initialization routines, new threads
can be
created, but they do not begin execution until DLL initialization is done
for the
process.
• Only one thread in a process can be in a DLL initialization or
detach routine at a
time.
Threads and Synchronization 4
• ExitProcess
does not return until no threads are in their DLL initialization or
detach routines.
A thread that uses functions from the static C run-time libraries should use
the
beginthread and
endthread
C run-time functions for thread management rather than
CreateThread and
ExitThread. Failure to do so results in small memory leaks when
ExitThread is called. Note that this is not a problem with the
C run-time in a DLL.
26.2 Thread’s Example
enum Shape { RECTANGLE, ELLIPSE };
DWORD WINAPI drawThread(LPVOID shape);
SYSTEMTIME st;
hThread1 = CreateThread(NULL, 0,
drawThread,
(LPVOID)RECTANGLE, CREATE_SUSPENDED,
&dwThread1
);
hThread2 = CreateThread(NULL, 0,
drawThread, (LPVOID)ELLIPSE,
CREATE_SUSPENDED, &dwThread2
);
hDC = GetDC(hWnd);
hBrushRectangle=CreateSolidBrush(RGB(170,220,160));
hBrushEllipse = CreateHatchBrush(HS_BDIAGONAL,RGB(175,180,225));
InitializeCriticalSection(&cs);
srand( (unsigned)time(NULL) );
ResumeThread(hThread2);
ResumeThread(hThread1);
26.2.1 Thread Procedure
DWORD WINAPI drawThread(LPVOID type)
{
int i;
if((enum Shape)type == RECTANGLE)
{
for(i=0; i<10000; ++i)
Threads and Synchronization 5
{
EnterCriticalSection(&cs);
SelectObject(hDC, hBrushRectangle);
Rectangle(hDC, 50, 1, rand()%300, rand()%100);
GetLocalTime(&st);
LeaveCriticalSection(&cs);
Sleep(10);
}
}
26.3 Synchronization
Using threads we can use lot of shared variables. These shared variables
maybe used by
a single thread further more these variables may also be used and changed by
several
parralle threads. If there are several threads operating at the same time
then a particular
DC handle can be used in one of the threads only. If we want to use a single
DC handle in
more then one thread, we use synchronization objects. Synchronization
objects prevent
other threads to use the shared data at the same.
To synchronize access to a resource, use one of the
synchronization objects in one
of the
wait functions. The state of a synchronization object is either
signaled or
nonsignaled.
The wait functions allow a thread to block its own execution until a
specified nonsignaled
object is set to the signaled state.
26.3.1 Overlapped Input and Output
You can perform either synchronous or asynchronous (or overlapped) I/O
operations on
files, named pipes, and serial communications devices. The
WriteFile,
ReadFile,
DeviceIoControl,
WaitCommEvent,
ConnectNamedPipe, and TransactNamedPipe
functions can be performed either synchronously or asynchronously. The
ReadFileEx
and WriteFileEx
functions can be performed asynchronously only.
When a function is executed synchronously, it does not return until the
operation has
been completed. This means that the execution of the calling thread can be
blocked for an
indefinite period while it waits for a time-consuming operation to finish.
Functions called
for overlapped operation can return immediately, even though the operation
has not been
completed. This enables a time-consuming I/O operation to be executed in the
background while the calling thread is free to perform other tasks. For
example, a single
thread can perform simultaneous I/O operations on different handles, or even
simultaneous read and write operations on the same handle.
To synchronize its execution with the completion of the overlapped
operation, the calling
thread uses the GetOverlappedResult function or one of the
wait functions to determine
when the overlapped operation has been completed. You can also use the
HasOverlappedIoCompleted
macro to poll for completion.
Threads and Synchronization 6
To cancel all pending asynchronous I/O operations, use the
CancelIo
function. This
function only cancels operations issued by the calling thread for the
specified file handle.
Overlapped operations require a file, named pipe, or communications device
that was
created with the FILE_FLAG_OVERLAPPED flag. To call a function to perform an
overlapped operation, the calling thread must specify a pointer to an
OVERLAPPEDstructure. If this pointer is NULL, the function return value may
incorrectly indicate that
the operation completed. The system sets the state of the event object to
nonsignaled
when a call to the I/O function returns before the operation has been
completed. The
system sets the state of the event object to signaled when the operation has
been
completed.
When a function is called to perform an overlapped operation, it is possible
that the
operation will be completed before the function returns. When this happens,
the results
are handled as if the operation had been performed synchronously. If the
operation was
not completed, however, the function's return value is FALSE, and the
GetLastError
function returns ERROR_IO_PENDING.
A thread can manage overlapped operations by either of two methods:
• Use the
GetOverlappedResult
function to wait for the overlapped operation to
be completed.
• Specify a handle to the
OVERLAPPED
structure's manual-reset event object in
one of the wait functions
and then call
GetOverlappedResult
after the wait
function returns. The
GetOverlappedResult function returns the results of the
completed overlapped operation, and for functions in which such information
is
appropriate, it reports the actual number of bytes that were transferred.
When performing multiple simultaneous overlapped operations, the calling
thread must
specify an OVERLAPPED
structure with a different manual-reset event object for each
operation. To wait for any one of the overlapped operations to be completed,
the thread
specifies all the manual-reset event handles as wait criteria in one of the
multiple-object
wait functions. The return value of the multiple-object wait
function indicates which
manual-reset event object was signaled, so the thread can determine which
overlapped
operation caused the wait operation to be completed.
If no event object is specified in the
OVERLAPPED
structure, the system signals the
state of the file, named pipe, or communications device when the overlapped
operation
has been completed. Thus, you can specify these handles as synchronization
objects in a
wait function, though their use for this purpose can be difficult to manage.
When
performing simultaneous overlapped operations on the same file, named pipe,
or
communications device, there is no way to know which operation caused the
object's
state to be signaled. It is safer to use a separate event object for each
overlapped
operation.
Threads and Synchronization 7
26.3.2 Asynchronous Procedure Call
An asynchronous procedure call (APC) is a function that executes
asynchronously in the
context of a particular thread. When an APC is queued to a thread, the
system issues a
software interrupt. The next time the thread is scheduled, it will run the
APC function.
APCs made by the system are called "kernel-mode APCs." APCs made by an
application
are called "user-mode APCs." A thread must be in an alertable state to run a
user-mode
APC.
Each thread has its own APC queue. An application queues an APC to a thread
by calling
the QueueUserAPC
function. The calling thread specifies the address of an APC
function in the call to QueueUserAPC. The queuing of an APC is a
request for the
thread to call the APC function.
When a user-mode APC is queued, the thread to which it is queued is not
directed to call
the APC function unless it is in an alertable state. A thread enters an
alertable state when
it calls the SleepEx, SignalObjectAndWait,
MsgWaitForMultipleObjectsEx,
WaitForMultipleObjectsEx, or WaitForSingleObjectEx
function. Note that you
cannot use WaitForSingleObjectEx to wait on the handle to the
object for which the
APC is queued. Otherwise, when the asynchronous operation is completed, the
handle is
set to the signaled state and the thread is no longer in an alertable wait
state, so the APC
function will not be executed. However, the APC is still queued, so the APC
function will
be executed if you call another alertable wait function.
Note that the ReadFileEx,
SetWaitableTimer, and WriteFileEx
functions are
implemented using an APC as the completion notification callback mechanism.
26.3.3 Critical Section
Critical section objects provide synchronization similar to that
provided by mutex
objects, except that critical section objects can be used only by the
threads of a single
process. Event, mutex, and semaphore objects can also be used in a
single-process
application, but critical section objects provide a slightly faster, more
efficient
mechanism for mutual-exclusion synchronization (a processor-specific test
and set
instruction). Like a mutex object, a critical section object can be owned by
only one
thread at a time, which makes it useful for protecting a shared resource
from
simultaneous access. There is no guarantee about the order in which threads
will obtain
ownership of the critical section; however, the system will be fair to all
threads. Unlike a
mutex object, there is no way to tell whether a critical section has been
abandoned.
The process is responsible for allocating the memory used by a critical
section. Typically,
this is done by simply declaring a variable of type CRITICAL_SECTION. Before
the
threads of the process can use it, initialize the critical section by using
the
InitializeCriticalSection
or InitializeCriticalSectionAndSpinCount
function.
Threads and Synchronization 8
A thread uses the EnterCriticalSection or
TryEnterCriticalSection
function to request
ownership of a critical section. It uses the
LeaveCriticalSection
function to release
ownership of a critical section. If the critical section object is currently
owned by another
thread, EnterCriticalSection waits indefinitely for ownership.
In contrast, when a mutex
object is used for mutual exclusion, the
wait functions
accept a specified time-out
interval. The TryEnterCriticalSection function attempts to
enter a critical section
without blocking the calling thread.
Once a thread owns a critical section, it can make additional calls to
EnterCriticalSection or
TryEnterCriticalSection
without blocking its execution. This
prevents a thread from deadlocking itself while waiting for a critical
section that it
already owns. To release its ownership, the thread must call
LeaveCriticalSection
once
for each time that it entered the critical section.
A thread uses the
InitializeCriticalSectionAndSpinCount or
SetCriticalSectionSpinCount
function to specify a spin count for the critical section
object. On single-processor systems, the spin count is ignored and the
critical section spin
count is set to 0. On multiprocessor systems, if the critical section is
unavailable, the
calling thread will spin
dwSpinCount
times before performing a wait operation on a
semaphore associated with the critical section. If the critical section
becomes free during
the spin operation, the calling thread avoids the wait operation.
Any thread of the process can use the
DeleteCriticalSection
function to release the
system resources that were allocated when the critical section object was
initialized. After
this function has been called, the critical section object can no longer be
used for
synchronization.
When a critical section object is owned, the only other threads affected are
those waiting
for ownership in a call to
EnterCriticalSection. Threads that are not waiting are free to
continue running.
26.4 Wait Functions
The wait functions
to allow a thread to block its own execution. The wait functions do not
return until the specified criteria have been met. The type of wait function
determines the
set of criteria used. When a wait function is called, it checks whether the
wait criteria
have been met. If the criteria have not been met, the calling thread enters
the wait state. It
uses no processor time while waiting for the criteria to be met.
There are four types of wait functions:
• single-object
• multiple-object
• alertable
• registered
Threads and Synchronization 9
Single-object Wait Functions
The SignalObjectAndWait, WaitForSingleObject, and
WaitForSingleObjectEx
functions require a handle to one synchronization object. These
functions return when
one of the following occurs:
• The specified object is in the signaled state.
• The time-out interval elapses. The time-out interval can be set to
INFINITE to
specify that the wait will not time out.
The
SignalObjectAndWait function enables the calling thread to
atomically set the state
of an object to signaled and wait for the state of another object to be set
to signaled.
Multiple-object Wait Functions
The WaitForMultipleObjects, WaitForMultipleObjectsEx,
MsgWaitForMultipleObjects, and
MsgWaitForMultipleObjectsEx
functions enable
the calling thread to specify an array containing one or more
synchronization object
handles. These functions return when one of the following occurs:
• The state of any one of the specified objects is set to signaled or
the states of all
objects have been set to signaled. You control whether one or all of the
states will
be used in the function call.
• The time-out interval elapses. The time-out interval can be set to
INFINITE to
specify that the wait will not time out.
The
MsgWaitForMultipleObjects and
MsgWaitForMultipleObjectsEx
function allow
you to specify input event objects in the object handle array. This is done
when you
specify the type of input to wait for in the thread's input queue.
For example, a thread could use
MsgWaitForMultipleObjects
to block its execution
until the state of a specified object has been set to signaled and there is
mouse input
available in the thread's input queue. The thread can use the
GetMessage
or
PeekMessage function to retrieve the input.
When waiting for the states of all objects to be set to signaled, these
multiple-object
functions do not modify the states of the specified objects until the states
of all objects
have been set signaled. For example, the state of a mutex object can be
signaled, but the
calling thread does not get ownership until the states of the other objects
specified in the
array have also been set to signaled. In the meantime, some other thread may
get
ownership of the mutex object, thereby setting its state to nonsignaled.
Alertable Wait Functions
The
MsgWaitForMultipleObjectsEx,
SignalObjectAndWait,
WaitForMultipleObjectsEx,
and WaitForSingleObjectEx
functions differ from the
Threads and Synchronization 10
other wait functions in that they can optionally perform an
alertable wait operation.
In an
alertable wait operation, the function can return when the specified
conditions are met,
but it can also return if the system queues an I/O completion routine or an
APC for
execution by the waiting thread. For more information about alertable wait
operations
and I/O completion routines. See Synchronization and Overlapped Input and
Output. For
more information about APCs, see Asynchronous Procedure Calls that is
already
described in our above section
Synchronization.
Registered Wait Functions
The
RegisterWaitForSingleObject function differs from the other wait
functions in that
the wait operation is performed by a thread from the thread pool. When the
specified
conditions are met, the callback function is executed by a worker thread
from the thread
pool.
By default, a registered wait operation is a multiple-wait operation. The
system resets the
timer every time the event is signaled (or the time-out interval elapses)
until you call the
UnregisterWaitEx function to cancel the operation. To specify
that a wait operation
should be executed only once, set the
dwFlags
parameter of
RegisterWaitForSingleObject
to WT_EXECUTEONLYONCE.
Wait Functions and Synchronization ObjectsThe wait functions can modify
the states of some types of synchronization objects.
Modification occurs only for the object or objects whose signaled state
caused the
function to return. Wait functions can modify the states of synchronization
objects as
follows:
• The count of a semaphore object decreases by one, and the state of
the semaphore
is set to nonsignaled if its count is zero.
• The states of mutex, auto-reset event, and change-notification
objects are set to
nonsignaled.
• The state of a synchronization timer is set to nonsignaled.
• The states of manual-reset event, manual-reset timer, process,
thread, and console
input objects are not affected by a wait function.
Wait Functions and Creating Windows
You have to be careful when using the wait functions and code that directly
or indirectly
creates windows. If a thread creates any windows, it must process messages.
Message
broadcasts are sent to all windows in the system. If you have a thread that
uses a wait
function with no time-out interval, the system will deadlock. Two examples
of code that
indirectly creates windows are DDE and COM
CoInitialize. Therefore, if you have a
thread that creates windows, use
MsgWaitForMultipleObjects or
MsgWaitForMultipleObjectsEx, rather than the other wait
functions.
Threads and Synchronization 11
26.5 Synchronization Objects
A synchronization object
is an object whose handle can be specified in one of the
wait
functions to coordinate the execution of multiple threads. More
than one process can have
a handle to the same synchronization object, making interprocess
synchronization
possible.
The following object types are provided exclusively for synchronization.
Type Description
Event Notifies one or more waiting threads that an event has occurred.
Mutex Can be owned by only one thread at a time, enabling threads to
coordinate
mutually exclusive access to a shared resource.
Semaphore Maintains a count between zero and some maximum value, limiting
the
number of threads that are simultaneously accessing a shared resource.
Waitable
timer Notifies one or more waiting threads that a specified time has
arrived.
Though available for other uses, the following objects can also be used for
synchronization.
Object Description
Change
notification
Created by the
FindFirstChangeNotification function, its state is set to
signaled when a specified type of change occurs within a specified
directory or directory tree.
Console input
Created when a console is created. The handle to console input is returned
by the CreateFile
function when CONIN$ is specified, or by the
GetStdHandle function. Its state is set to signaled when there is
unread
input in the console's input buffer, and set to nonsignaled when the input
buffer is empty.
Job
Created by calling the
CreateJobObject function. The state of a job object
is set to signaled when all its processes are terminated because the
specified end-of-job time limit has been exceeded.
Memory
resource
notification
Created by the
CreateMemoryResourceNotification function. Its state is
set to signaled when a specified type of change occurs within physical
memory.
Process
Created by calling the
CreateProcess function. Its state is set to
nonsignaled while the process is running, and set to signaled when the
process terminates.
Thread
Created when a new thread is created by calling the
CreateProcess,
CreateThread, or
CreateRemoteThread
function. Its state is set to
nonsignaled while the thread is running, and set to signaled when the
thread terminates.
Threads and Synchronization 12
In some circumstances, you can also use a file, named pipe, or
communications device as
a synchronization object; however, their use for this purpose is
discouraged. Instead, use
asynchronous I/O and wait on the event object set in the
OVERLAPPED
structure. It is
safer to use the event object because of the confusion that can occur when
multiple
simultaneous overlapped operations are performed on the same file, named
pipe, or
communications device. In this situation, there is no way to know which
operation caused
the object's state to be signaled.
26.5.1 Mutex Object
The CreateMutex
function creates or opens a named or unnamed mutex object.
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES
lpMutexAttributes,/*null
if default security
attributes*/
BOOL
bInitialOwner, /*is
the current thread is the initialize owner*/
LPCTSTR
lpName /*name of the named mutex object*/
);
lpMutexAttributes: Pointer to a SECURITY_ATTRIBUTES
structure that determines
whether the returned handle can be inherited by child processes. If
lpMutexAttributes
is
NULL, the handle cannot be inherited.
The
lpSecurityDescriptor member of the structure specifies a security
descriptor
for the new mutex. If
lpMutexAttributes is NULL, the mutex gets a default
security descriptor. The ACLs in the default security descriptor for a mutex
come
from the primary or impersonation token of the creator.
bInitialOwner: If this value is TRUE and the caller created the
mutex, the calling thread
obtains initial ownership of the mutex object. Otherwise, the calling thread
does not
obtain ownership of the mutex.
lpName: Pointer to a null-terminated string specifying the name
of the mutex object. The
name is limited to MAX_PATH characters. Name comparison is case sensitive.
If lpName
matches the name of an existing named mutex object, this function
requests the MUTEX_ALL_ACCESS access right. In this case, the
bInitialOwner
parameter is ignored because it has already been set by the creating
process. If the
lpMutexAttributes
parameter is not NULL, it determines whether the handle can
be inherited, but its security-descriptor member is ignored.
If lpName
is NULL, the mutex object is created without a name.
If lpName
matches the name of an existing event, semaphore, waitable timer, job,
or file-mapping object, the function fails and the
GetLastError
function returns
ERROR_INVALID_HANDLE. This occurs because these objects share the same
name space.
Threads and Synchronization 13
Terminal Services: The name can have a "Global\" or "Local\"
prefix to
explicitly create the object in the global or session name space. The
remainder of
the name can contain any character except the backslash character (\).
Return Values:
If the function succeeds, the return value is a handle to the mutex object.
If the
named mutex object existed before the function call, the function returns a
handle
to the existing object and
GetLastError returns ERROR_ALREADY_EXISTS.
Otherwise, the caller created the mutex.
The handle returned by
CreateMutex has the MUTEX_ALL_ACCESS access right and
can be used in any function that requires a handle to a mutex object.
Any thread of the calling process can specify the mutex-object handle in a
call to one of
the wait functions. The
single-object wait functions return when the state of the specified
object is signaled. The multiple-object wait functions can be instructed to
return either
when any one or when all of the specified objects are signaled. When a wait
function
returns, the waiting thread is released to continue its execution.
The state of a mutex object is signaled when it is not owned by any thread.
The creating
thread can use the
bInitialOwner flag to request immediate ownership of the mutex.
Otherwise, a thread must use one of the wait functions to request ownership.
When the
mutex's state is signaled, one waiting thread is granted ownership, the
mutex's state
changes to nonsignaled, and the wait function returns. Only one thread can
own a mutex
at any given time. The owning thread uses the
ReleaseMutex function to release its
ownership.
The thread that owns a mutex can specify the same mutex in repeated wait
function calls
without blocking its execution. Typically, you would not wait repeatedly for
the same
mutex, but this mechanism prevents a thread from deadlocking itself while
waiting for a
mutex that it already owns. However, to release its ownership, the thread
must call
ReleaseMutex once for each time that the mutex satisfied a wait.
Two or more processes can call
CreateMutex
to create the same named mutex. The first
process actually creates the mutex, and subsequent processes open a handle
to the
existing mutex. This enables multiple processes to get handles of the same
mutex, while
relieving the user of the responsibility of ensuring that the creating
process is started first.
When using this technique, you should set the
bInitialOwner
flag to FALSE; otherwise, it
can be difficult to be certain which process has initial ownership.
Multiple processes can have handles of the same mutex object, enabling use
of the object
for interprocess synchronization. The following object-sharing mechanisms
are available:
• A child process created by the CreateProcess
function can inherit a handle to a
mutex object if the
lpMutexAttributes parameter of
CreateMutex
enabled
inheritance.
Threads and Synchronization 14
• A process can specify the mutex-object handle in a call to the
DuplicateHandlefunction to create a duplicate handle that can be used by
another process.
• A process can specify the name of a mutex object in a call to the
OpenMutex or
CreateMutex function.
Use the CloseHandle
function to close the handle. The system closes the handle
automatically when the process terminates. The mutex object is destroyed
when its last
handle has been closed.
26.6 Thread Example Using Mutex Object
hThread1= CreateThread(NULL, 0, drawThread, (LPVOID)RECTANGLE,
CREATE_SUSPENDED, &dwThread1);
hThread2 = .. .. ..
hBrushRectangle = CreateSolidBrush(RGB(170,220,160));
hBrushEllipse=CreateHatchBrush(HS_BDIAGONAL,RGB(175,180,225));
hMutex=CreateMutex(NULL, 0, NULL);
srand( (unsigned)time(NULL) );
ResumeThread(hThread2);
for(i=0; i<10000; ++i)
{
Switch(WaitForSingleObject(hMutex, INFINITE))
{
case WAIT_OBJECT_0:
SelectObject(hDC, hBrushRectangle);
Rectangle(hDC, 50, 1, rand()%300, rand()%100);
GetLocalTime(&st);
ReleaseMutex(hMutex);
Sleep(10);
};
26.7 Checking if the previous application is running
Using Named Mutex object you can check the application instance whether it
is already
running or not. Recreating the named mutex open the previous mutex object
but set last
error to ERROR_ALREADY_EXIST. You can check GetLastError if it is
ERROR_ALREADY_EXIST, then it is already running.
Threads and Synchronization 15
26.8 Event Object
The CreateEvent
function creates or opens a named or unnamed event object.
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES
lpEventAttributes,
/*null for the default
security */
BOOL
bManualReset, /*manual
reset or automatically reset its state*/
BOOL
bInitialState, /*set
initialize state signaled or unsignalled*/
LPCTSTR
lpName /* nanme of the event object*/
);lpEventAttributes:
Pointer to a SECURITY_ATTRIBUTES
structure that determines
whether the returned handle can be inherited by child processes. If
lpEventAttributes
is
NULL, the handle cannot be inherited.
The lpSecurityDescriptor member of the structure specifies a
security descriptor
for the new event. If
lpEventAttributes is NULL, the event gets a default security
descriptor. The ACLs in the default security descriptor for an event come
from the
primary or impersonation token of the creator.
bManualReset: If this parameter is TRUE, the function creates a
manual-reset event
object which requires use of the
ResetEvent
function set the state to nonsignaled. If this
parameter is FALSE, the function creates an auto-reset event object, and
system
automatically resets the state to nonsignaled after a single waiting thread
has been
released.
bInitialState: If this parameter is TRUE, the initial state of
the event object is signaled;
otherwise, it is nonsignaled.
lpName: Pointer to a null-terminated string specifying the name
of the event object. The
name is limited to MAX_PATH characters. Name comparison is case sensitive.
If lpName
matches the name of an existing named event object, this function
requests the EVENT_ALL_ACCESS access right. In this case, the
bManualReset
and bInitialState
parameters are ignored because they have already been set by the
creating process. If the
lpEventAttributes parameter is not NULL, it determines
whether the handle can be inherited, but its security-descriptor member is
ignored.
If lpName is NULL, the event
object is created without a name.
If lpName
matches the name of an existing semaphore, mutex, waitable timer, job,
or file-mapping object, the function fails and the GetLastError
function returns
ERROR_INVALID_HANDLE. This occurs because these objects share the same
name space.
Threads and Synchronization 16
Return Values:
If the function succeeds, the return value is a handle to the event object.
If the
named event object existed before the function call, the function returns a
handle
to the existing object and GetLastError returns ERROR_ALREADY_EXISTS.
The handle returned by CreateEvent has the EVENT_ALL_ACCESS access
right and
can be used in any function that requires a handle to an event object.
Any thread of the calling process can specify the event-object handle in a
call to one of
the wait functions. The single-object wait functions return when the state
of the specified
object is signaled. The multiple-object wait functions can be instructed to
return either
when any one or when all of the specified objects are signaled. When a wait
function
returns, the waiting thread is released to continue its execution.
The initial state of the event object is specified by the
bInitialState parameter. Use the
SetEvent function to set the state of an event object to
signaled. Use the
ResetEventfunction to reset the state of an event object to nonsignaled.
When the state of a manual-reset event object is signaled, it remains
signaled until it is
explicitly reset to nonsignaled by the ResetEvent function. Any
number of waiting
threads, or threads that subsequently begin wait operations for the
specified event object,
can be released while the object's state is signaled.
When the state of an auto-reset event object is signaled, it remains
signaled until a single
waiting thread is released; the system then automatically resets the state
to nonsignaled. If
no threads are waiting, the event object's state remains signaled.
Multiple processes can have handles of the same event object, enabling use
of the object
for interprocess synchronization. The following object-sharing mechanisms
are available:
• A child process created by the CreateProcess
function can inherit a handle to an
event object if the
lpEventAttributes parameter of
CreateEvent
enabled
inheritance.
• A process can specify the event-object handle in a call to the
DuplicateHandle
function to create a duplicate handle that can be used by another
process.
• A process can specify the name of an event object in a call to the
OpenEvent or
CreateEvent function.
Use the CloseHandle
function to close the handle. The system closes the handle
automatically when the process terminates. The event object is destroyed
when its last
handle has been closed.
Threads and Synchronization 17
26.8.1 Using Event Object (Example)
Applications use event objects in a number of situations to notify a waiting
thread of the
occurrence of an event. For example, overlapped I/O operations on files,
named pipes,
and communications devices use an event object to signal their completion.
In the following example, an application uses event objects to prevent
several threads
from reading from a shared memory buffer while a master thread is writing to
that buffer.
First, the master thread uses the
CreateEvent function to create a manual-reset event
object. The master thread sets the event object to non-signaled when it is
writing to the
buffer and then resets the object to signaled when it has finished writing.
Then it creates
several reader threads and an auto-reset event object for each thread. Each
reader thread
sets its event object to signaled when it is not reading from the buffer.
#define NUMTHREADS 4
HANDLE hGlobalWriteEvent;
void CreateEventsAndThreads(void)
{
HANDLE hReadEvents[NUMTHREADS], hThread;
DWORD i, IDThread;
// Create a manual-reset event object. The master thread sets
// this to nonsignaled when it writes to the shared buffer.
hGlobalWriteEvent = CreateEvent(
NULL, // no security attributes
TRUE, // manual-reset event
TRUE, // initial state is signaled
"WriteEvent" // object name
);
if (hGlobalWriteEvent == NULL)
{
// error exit
}
// Create multiple threads and an auto-reset event object
// for each thread. Each thread sets its event object to
// signaled when it is not reading from the shared buffer.
for(i = 1; i <= NUMTHREADS; i++)
{
// Create the auto-reset event.
hReadEvents[i] = CreateEvent(
NULL, // no security attributes
FALSE, // auto-reset event
TRUE, // initial state is signaled
NULL); // object not named
if (hReadEvents[i] == NULL)
{
Threads and Synchronization 18
// Error exit.
}
hThread = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE) ThreadFunction,
&hReadEvents[i], // pass event handle
0, &IDThread);
if (hThread == NULL)
{
// Error exit.
}
}
}
Before the master thread writes to the shared buffer, it uses the
ResetEvent function to
set the state of hGlobalWriteEvent (an application-defined global variable)
to
nonsignaled. This blocks the reader threads from starting a read operation.
The master
then uses the
WaitForMultipleObjects function to wait for all reader threads to
finish
any current read operations. When
WaitForMultipleObjects returns, the master thread
can safely write to the buffer. After it has finished, it sets
hGlobalWriteEvent and all the
reader-thread events to signaled, enabling the reader threads to resume
their read
operations.
VOID WriteToBuffer(VOID)
{
DWORD dwWaitResult, i;
// Reset hGlobalWriteEvent to nonsignaled, to block readers.
if (! ResetEvent(hGlobalWriteEvent) )
{
// Error exit.
}
// Wait for all reading threads to finish reading.
dwWaitResult = WaitForMultipleObjects(
NUMTHREADS, // number of handles in array
hReadEvents, // array of read-event handles
TRUE, // wait until all are signaled
INFINITE); // indefinite wait
switch (dwWaitResult)
{
// All read-event objects were signaled.
case WAIT_OBJECT_0:
// Write to the shared buffer.
break;
// An error occurred.
default:
printf("Wait error: %d\n", GetLastError());
ExitProcess(0);
}
Threads and Synchronization 19
// Set hGlobalWriteEvent to signaled.
if (! SetEvent(hGlobalWriteEvent) )
{
// Error exit.
}
// Set all read events to signaled.
for(i = 1; i <= NUMTHREADS; i++)
if (! SetEvent(hReadEvents[i]) )
{
// Error exit.
}
}
Before starting a read operation, each reader thread uses
WaitForMultipleObjects to
wait for the application-defined global variable hGlobalWriteEvent and its
own read
event to be signaled. When
WaitForMultipleObjects returns, the reader thread's autoreset
event has been reset to nonsignaled. This blocks the master thread from
writing to
the buffer until the reader thread uses the
SetEvent function to set the event's state back
to signaled.
VOID ThreadFunction(LPVOID lpParam)
{
DWORD dwWaitResult;
HANDLE hEvents[2];
hEvents[0] = *(HANDLE*)lpParam; // thread's read event
hEvents[1] = hGlobalWriteEvent;
dwWaitResult = WaitForMultipleObjects(
2, // number of handles in array
hEvents, // array of event handles
TRUE, // wait till all are signaled
INFINITE); // indefinite wait
switch (dwWaitResult)
{
// Both event objects were signaled.
case WAIT_OBJECT_0:
// Read from the shared buffer.
break;
// An error occurred.
default:
printf("Wait error: %d\n", GetLastError());
ExitThread(0);
}
// Set the read event to signaled.
if (! SetEvent(hEvents[0]) )
{
// Error exit.
Threads and Synchronization 20
}
}
26.9 Semaphore Object
The CreateSemaphore
function creates or opens a named or unnamed semaphore object.
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES
lpSemaphoreAttributes,
LONG
lInitialCount,
LONG
lMaximumCount,
LPCTSTR
lpName
);lpSemaphoreAttributes:
Pointer to a SECURITY_ATTRIBUTES
structure that
determines whether the returned handle can be inherited by child processes.
If
lpSemaphoreAttributes is NULL, the handle cannot be inherited.
The lpSecurityDescriptor member of the structure specifies a
security descriptor
for the new semaphore. If
lpSemaphoreAttributes
is NULL, the semaphore gets a
default security descriptor. The ACLs in the default security descriptor for
a
semaphore come from the primary or impersonation token of the creator.
lInitialCount: Initial count for the semaphore object. This value
must be greater than or
equal to zero and less than or equal to
lMaximumCount. The state of a
semaphore is
signaled when its count is greater than zero and nonsignaled when it is
zero. The count is
decreased by one whenever a wait function releases a thread that was waiting
for the
semaphore. The count is increased by a specified amount by calling the
ReleaseSemaphore function.
lMaximumCount: Maximum count for the semaphore object. This value
must be greater
than zero.
lpName: Pointer to a null-terminated string specifying the name
of the semaphore object.
The name is limited to MAX_PATH characters. Name comparison is case
sensitive.
If lpName
matches the name of an existing named semaphore object, this function
requests the SEMAPHORE_ALL_ACCESS access right. In this case, the
lInitialCount and
lMaximumCount
parameters are ignored because they have
already been set by the creating process. If the
lpSemaphoreAttributes
parameter
is not NULL, it determines whether the handle can be inherited, but its
securitydescriptor
member is ignored.
If lpName
is NULL, the semaphore object is created without a name.
If lpName
matches the name of an existing event, mutex, waitable timer, job, or
file-mapping object, the function fails and the
GetLastError
function returns
Threads and Synchronization 21
ERROR_INVALID_HANDLE. This occurs because these objects share the same
name space.
Return Values:
If the function succeeds, the return value is a handle to the semaphore
object. If
the named semaphore object existed before the function call, the function
returns
a handle to the existing object and GetLastError returns
ERROR_ALREADY_EXISTS.
If the function fails, the return value is NULL. To get extended error
information,
call GetLastError.
The handle returned by CreateSemaphore has the SEMAPHORE_ALL_ACCESS
access right and can be used in any function that requires a handle to a
semaphore object.
Any thread of the calling process can specify the semaphore-object handle in
a call to one
of the wait functions. The single-object wait functions return when the
state of the
specified object is signaled. The multiple-object wait functions can be
instructed to return
either when any one or when all of the specified objects are signaled. When
a wait
function returns, the waiting thread is released to continue its execution.
The state of a semaphore object is signaled when its count is greater than
zero, and
nonsignaled when its count is equal to zero. The
lInitialCount parameter
specifies the
initial count. Each time a waiting thread is released because of the
semaphore's signaled
state, the count of the semaphore is decreased by one. Use the
ReleaseSemaphorefunction to increment a semaphore's
count by a specified amount. The count can never be
less than zero or greater than the value specified in the
lMaximumCount parameter.
Multiple processes can have handles of the same semaphore object, enabling
use of the
object for interprocess synchronization. The following object-sharing
mechanisms are
available:
• A child process created by the CreateProcess
function can inherit a handle to a
semaphore object if the
lpSemaphoreAttributes
parameter of
CreateSemaphore
enabled inheritance.
• A process can specify the semaphore-object handle in a call to the
DuplicateHandle function to create a duplicate handle that can be
used by
another process.
• A process can specify the name of a semaphore object in a call to
the
OpenSemaphore or
CreateSemaphore
function.
Use the CloseHandle
function to close the handle. The system closes the handle
automatically when the process terminates. The semaphore object is destroyed
when its
last handle has been closed.
Threads and Synchronization 22
26.10 Thread Local Storage (TLS)
Thread Local Storage (TLS) is the method by which each thread in a given
multithreaded
process may allocate locations in which to store thread-specific data.
Dynamically bound
(run-time) thread-specific data is supported by way of the TLS API
(TlsAlloc,
TlsGetValue, TlsSetValue, TlsFree). Win32 and the Visual C++ compiler, now
support
statically bound (load-time) per-thread data in addition to the existing API
implementation.
API Implementation for TLS
Thread Local Storage is implemented through the Win32 API layer as well as
the
compiler. For details, see the Win32 API documentation for
TlsAlloc,
TlsGetValue,
TlsSetValue, and
TlsFree.
The Visual C++ compiler includes a keyword to make thread local storage
operations
more automatic, rather than through the API layer. This syntax is described
in the next
section, Compiler Implementation for TLS.
Compiler Implementation for TLS
To support TLS, a new attribute,
thread, has been added to the C and C++ languages and
is supported by the Visual C++ compiler. This attribute is an extended
storage class
modifier, as described in the previous section. Use the
__declspec keyword to declare a
thread variable. For example, the following code declares an
integer thread local variable
and initializes it with a value:
__declspec( thread ) int tls_i = 1;
Summary
In this lecture, we studied about Threads and synchronization. To
synchronize
access to a resource, use one of the
synchronization objects in one
of the wait functions.
The state of a synchronization object is either
signaled or
nonsignaled. The wait
functions allow a thread to block its own execution until a specified
nonsignaled object is
set to the signaled state. Critical section objects provide synchronization
similar to that
provided by mutex objects, except that critical section objects can be used
only by the
threads of a single process. Event, mutex, and semaphore objects can also be
used in a
single-process. Another synchronization object is semaphore, events and
mutex. Threads
with synchronization problems have the best use in network applications.
Exercises
1. Create Thread to find factorial of any number. |