Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By MICKEY WILLIAMS and David Bennett

Win32 Processes, Threads, and Synchronization

A process is started by the operating system when an application is launched. The process owns the memory, resources, and threads of execution that are associated with a running instance of an executable program. When the process is started, one thread is initially associated with that process. As long as one thread continues to be associated with the process, the process continues to run.

Understanding Threads

A thread is the smallest schedulable unit of execution in a Windows application. A thread always is associated with a particular process—after the thread is started, it never runs in the context of another process. Although many simple applications use only a single thread, it's not uncommon for more complex applications to use multiple threads over the lifetime of the process.

Threads are scheduled according to their priority. Within a particular priority level, threads are scheduled in a circular (or round-robin) fashion. For example, if three threads are running at priority level 7, and these threads never yield, they will each get the same amount of execution time from the operating system.

The thread with the highest priority level is always running, assuming that it is not waiting for a resource to become available or for an event to occur. A maximum of one thread per system processor is allowed to be in the running state—all other threads are either waiting or are marked as ready. The ready state indicates that the thread is capable of running but is waiting to be scheduled to run.

After a thread begins to run, it continues to run until one of the following actions occur:

When a thread is prevented from running because its priority is lower than other threads in the system, it is said to be starved. Threads that run at lower priorities occasionally are given a "boost" in their priority in order to prevent deadlock.

The Need for Synchronization

After you introduce multiple threads into an application, you must plan for problems that simply don't exist in single-threaded programming. For example, the simple act of reading and writing to a global variable must be synchronized properly. Consider this innocent-looking code fragment:

int g_nQueuedRequests = 0;
void QueueRequest(void)
{
    ++g_nQueuedRequests;
    DoSomethingToQueueRequest();
}
void SatisfyRequest(void)
{
    —g_nQueuedRequests;
    DoSomethingToHandleRequestFromQueue()
}

This fragment contains two functions that write to the same variable. In a multithreaded application, it would be very easy to have two threads attempt to modify the variable simultaneously, which would cause the value for the variable to be corrupted. Two basic scenarios exist:

As illustrated by the preceding code fragment, synchronization is required for all types of data in your application. If it's possible for multiple threads to update a variable, you must provide some sort of synchronization for access.

The simplest types of synchronization primitives are used to manipulate or test the values of 32-bit scalar variables. The following Win32 functions are guaranteed to be atomic and thread safe, even when used with multiple processors:

Variables passed to these functions must be aligned on 32-bit boundaries (64-bit parameters must be aligned on a 64-bit boundary).

Share ThisShare This

Informit Network