[Previous] [Contents] [Next]

A Quick Review of Threads

A thread is a path of execution through a process. Every process has one or more threads of execution. As part of the Win32 process-creation function CreateProcess, an initial thread is automatically created. This initial thread begins execution at the main function (for console applications), WinMain function (for GUI applications), or ServiceMain function (for services). To create additional threads, a process can call the Win32 CreateThread function. Note that the system executes threads only, never processes. Threads enable an application to perform (or appear to perform) several tasks concurrently, which can lead to greater functionality in an application and improved responsiveness in the user interface. On machines with multiple processors, different threads can execute concurrently on different processors, yielding improved overall performance.

Normal function calls are synchronous, meaning that the caller must wait for the function to finish executing before it can proceed, as shown in the following code fragment:

// Some code here...
MyFunc();    // Call MyFunc.
// Some more code here...
// Code here executes only when MyFunc has finished.

The CreateThread function enables functions to execute asynchronously, meaning that the caller does not have to wait for the called function to finish executing, as shown in this code fragment:

// Some code here...
CreateThread(..., MyFunc, ...);    // Call MyFunc.
// Some more code here...
// Code here executes immediately;
// MyFunc is executing concurrently.

When a thread eventually finishes executing, you can call the GetExitCodeThread function to retrieve the thread's return value. A thread (MyFunc in the preceding code) terminates when it returns from the main thread function or when it explicitly calls ExitThread. (You can also use the Win32 TerminateThread function to kill a misbehaving thread, but this is not recommended.)

All kernel objects, including threads, have an associated usage counter. For threads, this usage counter is initially set to 2. When a thread terminates, its usage counter is implicitly decremented. The thread's creator should also decrement its usage counter using the Win32 CloseHandle function. In this way, the thread object's usage counter will reach 0 and the system will automatically destroy the thread object.

Priority values are assigned to each process and thread. The kernel's thread scheduler uses these values as part of its algorithm to determine the order in which threads should be scheduled for execution. You can adjust the priority value assigned to a process by calling the SetPriorityClass function, and you can call SetThreadPriority to control the priority of a thread. The two values are combined to arrive at the actual priority value of a thread, called the thread's base priority. You can call the corresponding GetPriorityClass and GetThreadPriority functions at any time to obtain the current settings.

One of the biggest challenges associated with the safe use of threads is synchronization. Any unprotected access to data shared by multiple threads is an access violation waiting to happen. For example, if two threads manipulate an unprotected data structure, data is likely to become corrupted. It is easy to envision a scenario in which one thread might begin to read some data only to be preempted by the scheduler, which might execute another thread that begins to modify the data in the shared structure. When execution focus eventually returns to the first thread, the data might no longer be consistent. This scenario will likely end in an exception.

This type of synchronization problem is difficult to correct because it can happen infrequently and seemingly at random, making it difficult to reproduce. Win32 offers several options for thread synchronization: events, critical sections, semaphores, and mutual exclusion (mutex) objects. Since data itself cannot be protected against concurrent access by different threads, you must write intelligent code that prevents this from happening. You can use the Win32 thread synchronization objects to guarantee that only one thread at a time executes a particular section of crucial code. Note that only data shared by multiple threads are at risk. Automatic variables located on the stack of a thread pose no difficulties because they are allocated on a per-thread basis; the same is true of thread local storage (TLS). Windows also provides support for fibers—lightweight, user-mode threads that must be manually scheduled—but these have little bearing on COM+, and so are not covered here.