Share via


Writing a Multithreaded Win32 Program

OverviewHow Do ISample

When you write a program with multiple threads, you must coordinate their behavior and use of the program’s resources. You must also make sure that each thread receives its own stack.

Sharing Common Resources Between Threads

Note   For a similar discussion from the MFC point of view, see Multithreading: Programming Tips and Multithreading: When to Use the Synchronization Classes.

Each thread has its own stack and its own copy of the CPU registers. Other resources, such as files, static data, and heap memory, are shared by all threads in the process. Threads using these common resources must be synchronized. Win32 provides several ways to synchronize resources, including semaphores, critical sections, events, and mutexes.

When multiple threads are accessing static data, your program must provide for possible resource conflicts. Consider a program where one thread updates a static data structure containing x,y coordinates for items to be displayed by another thread. If the update thread alters the x coordinate and is preempted before it can change the y coordinate, the display thread may be scheduled before the y coordinate is updated. The item would be displayed at the wrong location. You can avoid this problem by using semaphores to control access to the structure.

A mutex (short for mutual exclusion) is a way of communicating among threads or processes that are executing asynchronously of one another. This communication is usually used to coordinate the activities of multiple threads or processes, typically by controlling access to a shared resource by “locking” and “unlocking” the resource. To solve this x,y coordinate update problem, the update thread would set a mutex indicating that the data structure is in use before performing the update. It would clear the mutex after both coordinates had been processed. The display thread must wait for the mutex to be clear before updating the display. This process of waiting for a mutex is often called “blocking” on a mutex because the process is blocked and cannot continue until the mutex clears.

The BOUNCE.C program shown in the previous section uses a mutex named ScreenMutex to coordinate screen updates. Each time one of the display threads is ready to write to the screen, it calls WaitForSingleObject with the handle to ScreenMutex and constant INFINITE to indicate that the WaitForSingleObject call should block on the mutex and not time out. If ScreenMutex is clear, the wait function sets the mutex so other threads cannot interfere with the display and continues executing the thread. Otherwise, the thread blocks until the mutex clears. When the thread completes the display update, it releases the mutex by calling ReleaseMutex.

Screen displays and static data are only two of the resources requiring careful management. For example, your program may have multiple threads accessing the same file. Because another thread may have moved the file pointer, each thread must reset the file pointer before reading or writing. In addition, each thread must make sure that it is not preempted between the time it positions the pointer and the time it accesses the file. These threads should use a semaphore to coordinate access to the file by bracketing each file access with WaitForSingleObject and ReleaseMutex calls. The following code fragment illustrates this technique:

HANDLE    hIOMutex= CreateMutex (NULL, FALSE, NULL);

WaitForSingleObject( hIOMutex, INFINITE );
fseek( fp, desired_position, 0L );
fwrite( data, sizeof( data ), 1, fp );
ReleaseMutex( hIOMutex);

Thread Stacks

All of an application’s default stack space is allocated to the first thread of execution, which is known as thread 1. As a result, you must specify how much memory to allocate for a separate stack for each additional thread your program needs. The operating system will allocate additional stack space for the thread, if necessary, but you must specify a default value.

The first argument in the _beginthread call is a pointer to the BounceProc function, which will execute the threads. The second argument specifies the default stack size for the thread. The last argument is an ID number that is passed to BounceProc. BounceProc uses the ID number to seed the random number generator and to select the thread’s color attribute and display character.

Threads that make calls to the C run-time library or to the Win32 API must allow sufficient stack space for the library and API functions they call. The C printf function requires more than 500 bytes of stack space, and you should have 2K of stack space available when calling Win32 API routines.

Because each thread has its own stack, you can avoid potential collisions over data items by using as little static data as possible. Design your program to use automatic stack variables for all data that can be private to a thread. The only global variables in the BOUNCE.C program are either mutexes or variables that never change after they are initialized.

Win32 also provides Thread-Local Storage (TLS) to store per-thread data. See Thread Local Storage for more information.