Multithreading Do's and Don'ts

Let's go over the main pitfalls to avoid and practices to follow to help you multithread safely and efficiently. Do create threadsafe resource classes when resources need to be shared by different threads. Then you can encapsulate access synchronization to the resource. This may be sufficient to avoid reentrancy problems with shared resources or stateless variables.

When there is a persistent state involved (such as a series of calculations), if the threads are not designed to cooperate, you need per-thread instances of the resource or variable. You can use the Win32 calls TlsAlloc, TlsSetValue, TlsGetValue, and TlsFree to allocate memory on a per-thread basis. These functions support dynamic allocation of thread-specific memory for shared data. The compiler also provides static support for thread-specific data. This was mentioned above in regard to state data and AFX_MANAGE_STATE. You can also use the CThreadLocal template class to provide thread local storage on data derived from CNoTrackObject:


struct CMyThreadData : public CNoTrackObject{
CString strThread;
};
CThreadLocal<CMyThreadData> threadData;

Don't create threads with the C runtime functions beginthread or beginthreadex if the threads will access
any MFC objects. The framework uses CWinThread to manage thread-specific
data. You should use AfxBeginThread to start your threads (or call the CWinThread constructor and its CreateThread method). CWinThread::CreateThread eventually calls beginthreadex after taking care of some setup and synchronization.

Don't expect to be able to access any MFC objects not created within your thread. Each thread gets its own handle map within MFC, which means, for instance, that a handle to a CWnd passed to a thread method may be invalid inside the thread. Instead, pass handles to threads as their native HANDLE type, and then use the FromHandle or Attach methods to obtain a handle to an MFC object. Sometimes you don't have to go to this much trouble. CThreadSafeWnd::PaintBall, for instance, takes a pointer to a CWnd as an argument and works fine. It would take delving deeply into Windows internals to determine which situations require special handling and which don't. I suggest that you try the simple approach first and then, if that doesn't work, resort to FromHandle or Attach.

Do plan each thread's access to objects created by other threads. A thread can only access objects that it creates or that are passed to it. For an example of what I mean, refer back to CThreadSafeWnd. The threads responsible for the balls need to call the PaintBall member of the CThreadSafeWnd object (which I'll call m_tsw) to display the balls. Since they didn't create the m_tsw object, they need another way of getting at it. Make m_tsw a member
of the application class and use the AfxGetApp macro to obtain a pointer, as shown in Figure 6. Generally speaking, having clearly defined flows for any data shared between threads will make life easier.

Figure 6 Sharing Data


class CThreadApp : public CWinApp
{
public:
        CThreadSafeWnd* getThreadSafeWindow() const {return &m_tsw;} 
        CThreadApp();
private: 
        CThreadSafeWnd m_tsw;
// Override and implementation details go here
// ...
};
void CBallThread::SingleStep()
{
        // code to calculate the new position, m_rectPosition
        //...
        ((CThreadApp*) AfxGetApp())->
           getThreadSafeWindow()->PaintCircle(m_color, m_rectPosition);
}

Do avoid deadlock. This well known problem occurs when several threads are in competition for multiple resources, and each obtains a lock on one or more. The threads may end up waiting forever as shown in Figure 7, if you don't plan for this situation.

}

Figure 7 Deadlock

Threads that need multiple resources can request them with a CMultiLock object, which I'll discuss shortly. One forever, as shown in Figure 7, if you don't plan for this situation. Threads that need multiple resources can request them with a CMultiLock object, which I'll discuss shortly. One solution to the deadlock problem is to use the CMultiLock::Lock's timeout parameter, which will allow
at least one of the requesting threads to give up and release
its resources so other threads can proceed. Another
solution, if you have a common set of resources that are always required as a group, is to encapsulate them in a single threadsafe class and allocate them in an all-or-nothing fashion.