Multithreading is growing in importance in modern programming for a variety of reasons, not the least of which being that Windows supports multithreading. While C++ does not feature built-in support for multithreading, it can be used to created multithreaded programs, which is the subject of this article. It is taken from chapter three of The Art of C++, written by Herbert Schildt (McGraw-Hill/Osborne, 2004; ISBN: 0072255129).
Multithreading in C++ - An Overview of the Windows Thread Functions (Page 2 of 11 )
Windows offers a wide array of Application Programming Interface (API) functions that support multithreading. Many readers will be at least somewhat familiar with the multithreading functions offered by Windows, but for those who are not, an overview of those used in this chapter is presented here. Keep in mind that Windows provides many other multithreading-based functions that you might want to explore on your own.
To use Windowsí multithreading functions, you must include <windows.h> in your program.
Creating and Terminating a Thread
To create a thread, the Windows API supplies the CreateThread( ) function. Its prototype is shown here:
Here, secAttr is a pointer to a set of security attributes pertaining to the thread. However, if secAttr is NULL, then the default security descriptor is used.
Each thread has its own stack. You can specify the size of the new threadís stack in bytes using the stackSize parameter. If this integer value is zero, then the thread will be given a stack that is the same size as the creating thread. In this case, the stack will be expanded, if necessary. (Specifying zero is the common approach taken to thread stack size.)
Each thread of execution begins with a call to a function, called the thread function, within the creating process. Execution of the thread continues until the thread function returns. The address of this function (that is, the entry point to the thread) is specified in threadFunc. All thread functions must have this prototype:
DWORD WINAPI threadfunc(LPVOID param);
Any argument that you need to pass to the new thread is specified in CreateThread( )ís param. This 32-bit value is received by the thread function in its parameter. This parameter may be used for any purpose. The function returns its exit status.
The flags parameter determines the execution state of the thread. If it is zero, the thread begins execution immediately. If it is CREATE_SUSPEND, the thread is created in a suspended state, awaiting execution. (It may be started using a call to ResumeThread( ), discussed later.)
The identifier associated with a thread is returned in the long integer pointed to by threadID.
The function returns a handle to the thread if successful or NULL if a failure occurs. The thread handle can be explicitly destroyed by calling CloseHandle( ). Otherwise, it will be destroyed automatically when the parent process ends.
As just explained, a thread of execution terminates when its entry function returns. The process may also terminate the thread manually, using either TerminateThread( ) or ExitThread( ), whose prototypes are shown here:
For TerminateThread( ), thread is the handle of the thread to be terminated. ExitThread( ) can only be used to terminate the thread that calls ExitThread( ). For both functions, status is the termination status. TerminateThread( ) returns nonzero if successful and zero otherwise.
Calling ExitThread( ) is functionally equivalent to allowing a thread function to return normally. This means that the stack is properly reset. When a thread is terminated using TerminateThread( ), it is stopped immediately and does not perform any special cleanup activities. Also, TerminateThread( ) may stop a thread during an important operation. For these reasons, it is usually best (and easiest) to let a thread terminate normally when its entry function returns.
The Visual C++ Alternatives to CreateThread( ) and ExitThread( )
Although CreateThread( ) and ExitThread( ) are the Windows API functions used to create and terminate a thread, we wonít be using them in this chapter! The reason is that when these functions are used with Visual C++ (and possibly other Windows-compatible compilers), they can result in memory leaks, the loss of a small amount of memory. For Visual C++, if a multithreaded program utilizes C/C++ standard library functions and uses CreateThread( ) and ExitThread( ), then small amounts of memory are lost. (If your program does not use the C/C++ standard library, then no such losses will occur.) To eliminate this problem, you must use functions defined by the C/C++ runtime library to start and stop threads rather than those specified by the Win32 API. These functions parallel CreateThread( ) and ExitThread( ), but do not generate a memory leak.
Note If you are using a compiler other than Visual C++, check its documentation to determine if you need to bypass CreateThread( ) and ExitThread( ) and how to do so, if necessary.
The Visual C++ alternatives to CreateThread( ) and ExitThread( ) are _beginthreadex( ) and _endthreadex( ). Both require the header file <process.h>. Here is the prototype for _beginthreadex( ):
As you can see, the parameters to _beginthreadex( ) parallel those to CreateThread( ). Furthermore, they have the same meaning as those specified by CreateThread( ). secAttr is a pointer to a set of security attributes pertaining to the thread. However, if secAttr is NULL, then the default security descriptor is used. The size of the new threadís stack, in bytes, is passed in stackSize parameter. If this value is zero, then the thread will be given a stack that is the same size as the main thread of the process that creates it.
The address of the thread function (that is, the entry point to the thread) is specified in threadFunc. For _beginthreadex( ), a thread function must have this prototype:
unsigned __stdcall threadfunc(void * param);
This prototype is functionally equivalent to the one for CreateThread( ), but it uses different type names. Any argument that you need to pass to the new thread is specified in the param parameter.
The flags parameter determines the execution state of the thread. If it is zero, the thread begins execution immediately. If it is CREATE_SUSPEND, the thread is created in a suspended state, awaiting execution. (It may be started using a call to ResumeThread( ).) The identifier associated with a thread is returned in the double word pointed to by threadID.
The function returns a handle to the thread if successful or zero if a failure occurs. The type uintptr_t specifies a Visual C++ type capable of holding a pointer or handle.
The prototype for _endthreadex( ) is shown here:
void _endthreadex(unsigned status);
It functions just like ExitThread( ) by stopping the thread and returning the exit code specified in status.
Because the most widely used compiler for Windows is Visual C++, the examples in this chapter will use _beginthreadex( ) and _endthreadex( ) rather their equivalent API functions. If you are using a compiler other than Visual C++, simply substitute CreateThread( ) and EndThread( ).
When using _beginthreadex( ) and _endthreadex( ), you must remember to link in the multithreaded library. This will vary from compiler to compiler. Here are some examples. When using the Visual C++ command-line compiler, include the ĖMT option. To use the multithreaded library from the Visual C++ 6 IDE, first activate the Project | Settings property sheet. Then, select the C/C++ tab. Next, select Code Generation from the Category list box and then choose Multithreaded in the Use Runtime Library list box. For Visual C++ 7 .NET IDE, select Project | Properties. Next, select the C/C++ entry and highlight Code Generation. Finally, choose Multi-threaded as the runtime library.
Suspending and Resuming a Thread
A thread of execution can be suspended by calling SuspendThread( ). It can be resumed by calling ResumeThread( ). The prototypes for these functions are shown here:
For both functions, the handle to the thread is passed in hThread.
Each thread of execution has associated with it a suspend count. If this count is zero, then the thread is not suspended. If it is nonzero, the thread is in a suspended state. Each call to SuspendThread( ) increments the suspend count. Each call to ResumeThread( ) decrements the suspend count. A suspended thread will resume only when its suspend count has reached zero. Therefore, to resume a suspended thread implies that there must be the same number of calls to ResumeThread( ) as there have been calls to SuspendThread( ).
Both functions return the threadís previous suspend count or Ė1 if an error occurs.
Changing the Priority of a Thread
In Windows, each thread has associated with it a priority setting. A threadís priority determines how much CPU time a thread receives. Low priority threads receive little time. High priority threads receive a lot. Of course, how much CPU time a thread receives has a profound impact on its execution characteristics and its interaction with other threads currently executing in the system.
In Windows, a threadís priority setting is the combination of two values: the overall priority class of the process and the priority setting of the individual thread relative to that priority class. That is, a threadís actual priority is determined by combining the processís priority class with the threadís individual priority level. Each is examined next.