Multithreading Problems in Windows NT
Win32 vs. MFC
When programming under NT, it is important to understand the relationship between Win32 and MFC. The Windows NT operating system supports only one programming interface, which is in C and is called Win32. The Microsoft Foundation Classes (MFC) provide a C++ wrapper around some, but by no means all of Win32. MFC's wrapping of Win32 is fairly complete for the user interface part of Win32. For low-level operating system features, the wrapper becomes thinner or nonexistent. For example, there is no immediate MFC support for memory-mapped files, and, more importantly for the problem at hand, there is no MFC support for writing services. Needless to say, there are many C++ wrappers for certain parts of Win32 in the public domain. In fact, this article provides another one, for named pipes.
MFC programming is thus not overly useful when writing applications with no graphical user interface. Still, you may want to take advantage of utility classes such as CString. Unfortunately, the attempt to use MFC in a non-GUI multithreaded program is doomed to fail. To understand why, we must first take a closer look at multithreading under Windows NT.
NT Multithreading Caveats
There are some important caveats about multithreading in Windows NT. The first one is about the mysterious memory leak which people claim to have encountered with multithreading. Rest assured, Windows NT multithreading does not cause memory leaks. Every time you use a function that creates a kernel object, such as CreateThread, the operating systems allocates memory for that object, and you receive a handle to the object. You can then obtain further handles to the same object by calling one of the Open APIs such as OpenThread. Each object internally maintains a handle count. This count gets incremented every time someone obtains another handle to the object. The handle count can also be incremented internally by the system. A thread object and a process object, for example, have an internal count of a least 1 as long as the thread or process is running. To free a handle and decrement the handle count, an application must call the CloseHandle API.
The operating system frees the memory associated with the object as soon as the handle count drops to 0. For a thread, that means that the memory is freed when all handles to the thread have been closed and the thread has ended. So if a program creates new threads in an infinite loop, e.g., one new thread for each client, and does not close the handles, it will be a slow but steady memory guzzler. To demonstrate this, write a thread function that returns immediately. Then create threads with this function in an infinite loop in your main function, like this:
while(1) { CreateThread( // Pass the address of a function that returns immediately ) ; Sleep(0) ; }
The call to the Sleep API ensures that the program never has more than two threads active at any one time. The program will gobble up memory and end after a minute or so because of overstepping its quota. The problem will disappear if you close the handle that CreateThread returned.
The second caveat about threads concerns the use of multiple threads in conjunction with the C runtime library or with MFC. The runtime library was originally designed for a single-threaded environment. However, there are thread-safe versions of the RTL. If you use Microsoft Visual C++ as your development environment, here's what you must do:
1. Use the RTL _beginthreadex function instead of the CreateThread API.
2. Compile and link with the /MT option. To set this in the project settings dialog, go to the C/C++ tab, click Code Generation in the Category box and look at the "Use Run-Time Library" drop-down box.
The Win32 SDK documentation has different things to say about what happens if you neglect to follow this advice. I have been able to create access violations in the runtime library when printf was used heavily by multiple threads that were created with CreateThread.
The same problem of thread-safety occurs again when you use MFC. MFC keeps a lot of its behind-the-scene information on a per-thread basis. If you use multithreading with MFC, you must never create a thread directly with the CreateThread API or the _beginthreadex function. MFC provides the CWinThread class and the global AfxBeginThread function instead.
Now suppose that you are writing a multithreaded server-type application, and you want to use MFC utility classes such as CString. Since there will be no GUI, you certainly do not want to carry along MFC's message-pumping mechanism, and so you will write a C or C++ program that will not have a global CWinApp object. Since you are using MFC, you must start your threads as worker threads using AfxBeginThread. But alas, this leads to a failed assertion in line 75 of the MFC source file thrdcore.cpp: as part of starting the thread, MFC checks for the existence of the global CWinApp object.
So much for using MFC in services. The MFC documentation makes it quite clear that you are not supposed to use MFC at all unless you create all threads with CWinThread objects. o