Introduction to Win32 Threading: How To Thread the Needle Without Getting Stuck
- Creating and Using Threads
- Preventing Threading Problems
- Thread Termination
- From Here...
Threads are an integral part of Windows 32-bit programming (as well as 64-bit programming with future releases of Windows). You have more control and power over threading with C++ than with any other language (excluding assembly language). I use threads primarily in serial communication, but they can also be used when you need to process information and not tie up the user interface.
Some of the inherent problems associated with threading are race conditions, processor hogging, and failure of processes to terminate. After exploring how to create and use threads, we'll take a look at good general coding techniques to prevent these issues from cropping up.
Creating and Using Threads
To create a thread in the Win32 application programming interface (API), you call CreateThread. With Microsoft Foundation Classes (MFC), you call AFXBeginThread. You can also use the C runtime function _beginthread, which has fewer parameters than the CreateThread call. The thread function is the same for the Win32 API and MFC calls:
DWORD WINAPI MyThread (POVID pParam);
and just a little different for the _beginthread call:
void _cdecl MyThread (void * pParm);
For this article, let's use the Win32/MFC thread function. In most functions, because it's going to be running continuously, just use a "forever" loop:
DWORD WINAPI MyThread (POVID pParam) { extern bool threaddone; for (;;) { sleep(2); /// so we don't hog the processor or make a race condition if (threaddone) // variable set in another process so we know to exit return; } }
NOTE
There's a minor bug in the code above. Can you spot it? I'll point it out and fix it later in this article.
So now we create the thread. You can start the thread anywhere in your code. We'll use the MFC version to call the thread, since it takes the fewest parameters. (Yes, I know, a lot of you abhor MFC, but it's a decent class library for Win 32 apps, and it does use classes.)
The first parameter is the thread name; the second is the parameter to pass (if needed):
CWinThread * pMyThread AfxBeginThread(MyThread,&mythreadparam);
You can also set up your thread in a class and call it with the RUNTIME_CLASS macro in MFC:
CWinThread * pMyThread AfxBeginThread(RUNTIME_CLASS(CMyThread));
Another option is to set thread priority and call the thread in suspended mode. When setting priorities, you can use the options from the following table (found in the MSDN documentation) for the nPriority parameter.
Priority |
Meaning |
THREAD_PRIORITY_ABOVE_NORMAL |
Priority 1 point above the priority class. |
THREAD_PRIORITY_BELOW_NORMAL |
Priority 1 point below the priority class. |
THREAD_PRIORITY_HIGHEST |
Priority 2 points above the priority class. |
THREAD_PRIORITY_IDLE |
Base priority of 1 for IDLE_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, ABOVE_NORMAL_PRIORITY_CLASS, or HIGH_PRIORITY_CLASS processes, and a base priority of 16 for REALTIME_PRIORITY_CLASS processes. |
THREAD_PRIORITY_LOWEST |
Priority 2 points below the priority class. |
THREAD_PRIORITY_NORMAL |
Normal priority for the priority class. |
THREAD_PRIORITY_TIME_CRITICAL |
Base priority of 15 for IDLE_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, ABOVE_NORMAL_PRIORITY_CLASS, or HIGH_PRIORITY_CLASS processes, and a base priority of 31 for REALTIME_PRIORITY_CLASS processes. |
Most of the time, you should set your thread to THREAD_PRIORITY_NORMAL. Using a higher than normal priority will prevent other threads in your application from being processed equally with the higher-priority thread. This strategy can be useful in a real-time application, or even at the device-driver level. Be very careful when choosing a priority, as 99% of the time THREAD_PRIORITY_NORMAL is the one to choose.
dwCreateFlags can be either CREATE_SUSPENDED or 0. Here's an example in which the 0 param is used for the stack size (default):
CWinThread * pMyThread AfxBeginThread(MyThread,&mythreadparam, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED );
Now that the thread is started in suspended mode, we finish our init code and call pMyThread->ResumeThread to start it. I do this because I'm using the thread for serial port communications with an RS-232 device. This way, all the init code can get done, the app can come up, and then I can start the thread on my terms.
CAUTION
Don't use SuspendThread and ResumeThread in a ring 3 application. Those functions should only be used at ring 0 (for debuggers and the like), since you don't know what state the thread is in when you call those functions. That can cause deadlock.
Remember, many people who use your program are not computer-literate. They may get upset if the CPU fan stays on and the processor is pegged at 100%. For some reason, they think that a computer should be like a TV or toaster. Don't believe me? When you have some time to kill, go to your nearest "big box" store and watch the shoppers look at the computers. Count how many pick up the mouse and point it at the monitor, like you would a remote to a TV. Then go back to your office and spend the next few weeks (after pulling your hair out) trying to figure out how in the world you can write code that's so easy a 44-year-old (like me) can use it!