Introduction to win32 Thread Synchronization
There are a number of values we can supply to the SetThreadPriority function to adjust the priority level of indivudal threads.
Threads are created using THREAD_PRIORITY_NORMAL. This sets the thread’s priority to be equal to its process priority. The priority level of the thread can be adjusted after the fact using SetThreadPriority.
Race Conditions and Deadlocks
The Windows operating system switches between threads as it sees fit. As a result, the order of execution of multiple threads is unpredictable, since we do not know when the OS will switch from one thread to another. If a switch occurs during an operation that modifies state, that state may be corrupted. It is called a race condition because it is like a race to see which thread gets to modify the shared state first.
We set a number of threads running at the same time. Since they all begin with the same priority level, the computer tries to give each one equal time. It becomes a race to see which thread goes first, and each time the program is executed it varies.
Kernel objects are manipulated using handles.
Threads and Processes
Semaphores and mutexes
We can use semaphores, mutexes, and events to synchronize threads.
The Object Manager in the kernel manages the creation and deletionof kernel objects. The object’s lifetime is determined by the reference count maintained by the kernel.
The WaitForSingleObject() API function waits for a single kernel object to enter the signalled state. The first argument to the function is a handle to the object, and the second parameter is a DWORD value that specifies the time to wait. We can specify the value INFINITE here so that the function will wait without timing out.
Critical sections define a section of code that only one thread may be allowed to access at one time. Critical sections are a very basic synchronization mechanism. They’re very fast, but they are not kernel objects, and can only be access between threads in the same process.
We must initialize a critical section using InitializeCriticalSection before using a critical section. We start a critical section with EnterCriticalSection and we end a critical section with LeaveCriticalSection.
The following example makes use of the WaitForMultipleObjects function.
The first two parameters to WaitForMultipleObjects define an array of object handles. The third parameter defines a Boolean value that determines whether or not the function should wait for all the objects to become signalled before returning.
Mutexes are not as fast as critical sections, but they are more flexible. Unlike a critical section, a mutex is a kernel object, and so it can be used between processes. It’s also possible to define a timeout for a mutex, unlike for a critical section.
A mutex is owned by whoever is using the code guarded by a mutex.
We can create a mutex using the CreateMutex function. We can open an existing mutex using the OpenMutex function.