The life cycle of a thread begins when you call CreateThread. Other functions let you examine the thread, suspend or resume it, change its priority, and terminate it.
Any thread can create another thread by calling CreateThread. The arguments to CreateThread specify the properties a thread needs to begin life: primarily security privileges and a starting function. The starting function is to a thread what main or WinMain is to a full program. The thread’s life coincides with the life of its main function. When the function returns, the thread ends. A thread can start at any function that receives a single 32-bit parameter and returns a 32-bit value.
The parameter and return value are for your convenience. You need to declare them, but you don’t need to use them. CreateThread lets you pass a DWORD into the starting function. If several threads execute the same function, you might pass each one a different argument. Each might receive a pointer to a different filename, for example, or a different object handle to wait for.
CreateThread takes six parameters:
// prototype for the CreateThread function
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // access privileges
DWORD dwStackSize, // say 0 for default
LPTHREAD_START_ROUTINE lpStartAddress, // pointer to function
LPVOID lpParameter, // value passed to function
DWORD dwCreationFlags, // active or suspended
LPDWORD lpThreadId ); // system returns ID here
The first parameter points to a SECURITY_ATTRIBUTES structure that determines who may share the object and whether other processes may modify it. The structure contains a security descriptor that assigns access privileges for various system users and groups of users. Most programs simply accept the default descriptor that comes with the current process. The security structure also contains an inheritance flag. If you set the flag to TRUE, any child processes you create will automatically inherit a handle to this object.
typedef struct _SECURITY_ATTRIBUTES /* sa */
{
DWORD nLength; // size of(SECURITY_ATTRIBUTES)
LPVOID lpSecurityDescriptor; // NULL to accept process’s descriptor
BOOL bInheritHandle; // TRUE if children may inherit object
} SECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
You don’t need to create a SECURITY_ATTRIBUTES structure unless you want the thread to be inherited. If you pass NULL as the first parameter to CreateThread, the new thread receives the default descriptor and will not be inherited. If you do want to create a handle with limited access rights to its object, investigate the four SetSecurityDescriptor functions.
The next three parameters give the new thread material to work with. By default, each thread receives a stack the same size as that of the primary thread. You can change the size with the second parameter. If the stack later needs more room, the system expands it automatically. The third parameter points to the function where the thread will start, and the value in the fourth parameter becomes the argument passed to the starting function.
WARNING
Beware of using a local variable to pass a value to a new thread. The local variable will be destroyed when its procedure ends, and the thread may not have used it yet. Use global variables, allocate memory dynamically, or make the first thread wait for the new thread to terminate before it returns.
The dwCreationFlags parameter may be one of two values: either 0 or CREATE_ SUSPENDED. A suspended thread does not actually begin to run until you give it a push with ResumeThread. A program that creates a number of threads might suspend them, accumulate their handles, and, when ready, start them all off at once. That’s what the sample program later in the chapter does.
The last parameter points to an empty DWORD where CreateThread places a number to identify the thread uniquely in the system. A few functions require you to identify threads by their ID number instead of by their handles.
CreateThread returns a handle to the new thread. If the thread could not be created, the handle will be NULL. Be aware that the system will create the thread even if the lpStartAddress or lpParameter values are invalid or point to inaccessible data. In those cases, CreateThread returns a valid handle, but the new thread terminates immediately and returns an error code. You can test a thread’s viability with GetExitCodeThread, which returns STILL_ACTIVE if the thread has not ended.
Unless you give CreateThread an explicit security descriptor, the new handle comes with full access rights to the new object. In the case of threads, full access means that with this handle you can suspend, resume, terminate, or change the priority of the thread. The handle remains valid even after the thread terminates. To destroy the thread object, close its handle by calling CloseHandle. If more than one handle exists, the thread will not be destroyed until the last handle is closed. If you forget to close the handle, the system will do it automatically when your process ends.
An alternative method of creating threads using MFC is to create a class based on the CWinThread class. A skeletal example of the process follows:
// CThreadExample
IMPLEMENT_DYNCREATE(CThreadExample, CWinThread)
CThreadExample::CThreadExample()
{
... // class member variables are initialized
}
{
}
BOOL CThreadExample::InitInstance()
{
// TODO: perform any per-thread initialization here
...
// this is the point where non-variable initializations,
// such as creating instances of other class objects, are
// handled
return TRUE;
}
int CThreadExample::ExitInstance()
{
// TODO: perform any per-thread cleanup here
...
return CWinThread::ExitInstance();
}
BEGIN_MESSAGE_MAP(CThreadExample, CWinThread)
//{{AFX_MSG_MAP(CThreadExample)
// NOTE - ClassWizard will add/remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
A CWinThread object represents an execution thread within an application. Where the application’s main thread of execution is normally provided by a CWinApp-derived object, the CWinApp class itself is derived from CWinThread. Additional CWinThread objects, as shown in the example, allow multiple threads within the application.
MFC-based applications must use CWinThread-derived classes to ensure that the application is thread-safe. Under MFC, the framework uses thread local data to maintain thread-specific information for CWinThread-managed objects.
Furthermore, any thread created by the runtime function _beginthreadex cannot use any MFC APIs.
Two general types of threads are supported: working threads and user-interface threads. Working threads do not require a message handler. For example, a thread performing background calculations in a spreadsheet functions without any interaction with the user and does not need to respond to messages.
In contrast, user-interface threads must process messages received from the system (from the user) and require a message handler. User-interface threads can be derived from CWinApp or directly from the CWinThread class.
A CWinThread object commonly exists for the duration of the thread, but the behavior of the object may be modified by setting the m_bAutoDelete member to FALSE.
How to Create an MFC Thread
Threads are created by calling AfxBeginThread. For a user-interface thread, you call AfxBeginThread with a pointer to the CRuntimeClass of the CWinThread-derived class. For a working thread, you call AfxBeginThread with a pointer to the controlling function and the parameter for the controlling function.
For both working and user-interface threads, you may specify additional parameters to modify the priority, stack size, creation flags, and security attributes for the thread. AfxBeginThread returns a pointer to the new CWinThread object.
Alternatively, you can construct and then create a CWinThread-derived object by calling the CreateThread function for the class. Using this format permits the CWinThread-derived object to be reused between successively creating and terminating thread executions.
High-priority threads get more time on the processor, finish their work more quickly, and are more responsive to the user. But making all of your threads high priority entirely defeats the purpose of priorities. If a number of threads have the same priority—whether their priority is high or low—the scheduler must give them equal processor time. One thread can be more responsive only if the others are less responsive. The same rule applies equally to processes. Restrict your threads and processes as much as possible to low or average priority levels, and stay at high levels only as long as you must.
These functions retrieve or modify any thread’s base priority:
BOOL SetThreadPriority(
HANDLE hThread // a thread to modify
int iPriority ); // its new priority level
int GetThreadPriority( HANDLE hThread );
SetThreadPriority returns TRUE or FALSE for success or failure. GetThreadPriority returns the thread’s current priority. To name the possible priority values for both functions, use the following set of constants:
THREAD_PRIORITY_LOWEST Two levels below process THREAD_PRIORITY_BELOW_NORMAL One level below process
THREAD_PRIORITY_NORMALSame level as process
THREAD_PRIORITY_ABOVE_NORMAL One level above process
THREAD_PRIORITY_HIGHESTTwo levels above process
THREAD_PRIORITY_TIME_CRITICALFifteen (in normal user processes)
THREAD_PRIORITY_IDLEOne (in normal user processes)
The first five values adjust the thread’s base-priority level with respect to the level of its parent process, as shown earlier in Figure 14.2. The last two, for critical and idle priority, express absolute priority levels at the upper and lower extremes of the parent’s priority class. (The extremes for real-time priority codes are 16 and 31.) The idle priority level works well for screensavers because they should not execute unless nothing else is happening. Use the time critical level with extreme caution and only for short periods because it will starve lower-priority threads of processor time.
A suspended thread stops running and will not be scheduled for processor time. It remains in this state until some other thread makes it resume. Suspending a thread might be useful if, for example, the user interrupts a task. You could suspend the thread while waiting for the user to confirm the cancellation. If the user chooses to continue, the interrupted thread can resume where it left off. The Threads demo, discussed later in this chapter, suspends several drawing threads whenever the user resizes the window. Then when the window is repainted, the threads continue drawing.
A thread calls these functions to make another thread pause and resume:
DWORD SuspendThread( HANDLE hThread );
DWORD ResumeThread( HANDLE hThread );
A single thread may be suspended several times in succession without any intervening resume commands, but every SuspendThread command must eventually be matched with a ResumeThread command. The system counts the number of pending suspension commands for each thread (the thread suspension count attribute). SuspendThread increments the counter, and ResumeThread decrements it. Both functions return the previous value of the counter in a DWORD. Only when the counter returns to 0 does the thread resume execution.
A thread can suspend itself but cannot resume itself. However, a thread can put itself to sleep for a set amount of time. The Sleep command delays execution, removing the thread from the scheduler’s queue until some interval passes. Interactive threads that write or draw information for the user often take short naps to give the user time to see the output. Sleep is better than an empty loop because it doesn’t use processor time.
A thread calls these functions to pause for a set time:
VOID Sleep( DWORD dwMilliseconds );
DWORD SleepEx(
DWORD dwMilliseconds, // duration of pause
BOOL bAlertable ); // TRUE to resume if I/O operation finishes
The extended SleepEx function typically works in conjunction with background I/O functions and can be used to initiate a read or write operation without waiting for the operation to finish. The operation continues in the background. When it finishes, the system notifies the user by invoking a callback procedure from the program. Background I/O (also called overlapping I/O) is particularly helpful in interactive programs that must remain responsive to the user while working with relatively slow devices, such as tape drives and network disks.
The Boolean parameter in SleepEx lets the system wake the thread prematurely if an overlapping I/O operation finishes before the sleep interval expires. If SleepEx is interrupted, it returns WAIT_IO_COMPLETION. If the interval passes without interruption, SleepEx returns 0.
A thread can easily retrieve the two pieces of its own identity: a handle and an identifier.
These functions return information identifying the current thread:
DWORD GetCurrentThreadID( VOID );
HANDLE GetCurrentThread( VOID );
The return value from GetCurrentThreadID matches the value in lpIDThread after a CreateThread command. It is the value that uniquely identifies the thread to the system. Although few of the Win32 API commands require you to know a thread’s ID, it can be useful for monitoring threads system-wide without needing to keep handles open for each one. Open handles prevent threads from being destroyed.
The handle that GetCurrentThread returns serves the same purpose as the handle returned from CreateThread. Although it works in the same manner as other handles, it is actually a pseudohandle. A pseudohandle is a special constant that the system always interprets a certain way, much as a single dot (.) in DOS always refers to the current directory and this in C++ always points to the current object. The pseudohandle constant returned from GetCurrentThread always refers to the current thread. Unlike real handles, a pseudohandle does not work when passed to other threads. Here’s what a thread must do to acquire a real, transferrable handle to itself:
HANDLE hThread;
hThread = DuplicateHandle(
GetCurrentProcess(), // source process
GetCurrentThread(), // original handle
GetCurrentProcess(), // destination process
&hThread, // new duplicate handle
0, // access rights (overridden by last parameter)
DUPLICATE_SAME_ACCESS ); // copy access rights from original handle
While CloseHandle has no effect on a pseudohandle, the handle DuplicateHandle creates is real and must eventually be closed. Using pseudohandles lets GetCurrentThread work more quickly, because it assumes a thread should have full access to itself and returns its result without bothering about any security considerations.
Just as a Windows program ends when it comes to the end of WinMain, a thread normally meets its demise when it comes to the end of the function where it began. When a thread comes to the end of its starting function, the system automatically calls ExitThread, as:
VOID ExitThread( DWORD dwExitCode );
Although the system calls ExitThread automatically, you may call it directly if some condition forces a thread to an untimely end:
DWORD ThreadFunction( LPDWORD lpdwParam )
{
HANDLE hThread = CreateThread( <parameters> );
// initialization chores happen here
// test to see if there was a problem
if( <error condition> )
{
ExitThread( ERROR_CODE ); // cancel the thread
}
//
// no error, work continues
//
return( SUCCESS_CODE ); // this line causes the system
} // to call ExitThread
ERROR_CODE and SUCCESS_CODE are whatever you define them to be. In this simple example, you could just as easily have canceled with a return command:
if( <error condition> )
{
return( ERROR_CODE ); // cancel the thread
}
This return command has exactly the same effect as ExitThread; in fact, it even results in a call to ExitThread. The ExitThread command is genuinely useful for canceling from within any subroutines ThreadFunction calls.
When a thread ends at a return statement, the 32-bit return value becomes the exit code passed automatically to ExitThread. After a thread terminates, its exit code is available through this function:
// one thread calls this to find out how another ended
BOOL GetExitCodeThread( HANDLE hThread, LPDWORD lpdwExitCode );
GetExitCodeThread returns FALSE if an error prevents it from determining the return value.
ExitThread, whether called explicitly or implicitly as a consequence of return, permanently removes a thread from the dispatch queue and destroys the thread’s stack. It does not, however, destroy the thread object. That’s why you can still ask about the thread’s exit status even after the thread stops running. When possible, you should close thread handles explicitly (call CloseHandle) to avoid wasting space in memory. The system destroys a thread when its last handle is closed. The system will not destroy a running thread, even if all its handles are closed. (In that case, the thread is destroyed when it stops running.) If a process leaves handles open when it terminates, the system closes them automatically and removes orphaned objects no longer held by any process.
With ExitThread, a thread stops itself gracefully at a place of its own choosing. Another function allows one thread to stop another abruptly and arbitrarily:
// one thread calls this to stop another
BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode );
A thread cannot protect itself from termination. Anyone with a handle to the thread can force the thread to stop immediately, regardless of its current state (providing, of course, that the handle allows full access to the thread). Using the default security attributes in CreateThread produces a handle with full access privileges.
TerminateThread does not destroy the thread’s stack, but it does provide an exit code. Both ExitThread and TerminateThread set the thread object to its signaled state, so any other threads waiting for this one to end may proceed. After either command, the thread object lingers lifelessly until all its handles have been closed.
Several C runtime library commands duplicate some of the Win32 thread commands:
unsigned long _beginthread(
void( *start_address )( void * ), // starting function
unsigned stack_size, // initial stack size
void *arglist ); // parameter for starting function
void _endthread( void );
void _sleep( unsigned long ulMilliseconds );
_beginthread performs some of the internal initialization for a new thread that other C runtime functions, such as signal, depend on. The rule is consistency: If your program manipulates threads with C runtime functions, then use only C runtime functions wherever you have a choice. If your program uses Win32 functions with its threads, then stick to CreateThread and ExitThread. Also, if the thread calls C runtime functions, then create it with the C functions rather than the Win32 API. A few C routines require the initialization that _beginthread performs.