Click here to Skip to main content
15,885,365 members
Articles / Desktop Programming / MFC
Article

How to use the same thread function for multiple threads (safely)

Rate me:
Please Sign up or sign in to vote.
4.39/5 (13 votes)
28 May 20014 min read 168.5K   42   21
This article shows you how to subclass CWinThread to create a class that allows multiple instances to effectively use the same thread function, safely.

Introduction

My recent forray into threads and multithreaded applications has taught me a few lessons which I thought I'd pass on. As I suspect that most people know this already (it was just me taking a while to catch up), the target audience is beginners to the world of multithreading. If you're not a beginner, then please read this anyway and let me know if I've got it all wrong!

The Problem

My problem was that I wanted several threads to basically do the same thing. Being the OO developer that I am I wanted to be able to wrap this up in a class and just create several instances of that class and Bob's your uncle. Unfortunately, the function that you pass to AfxBeginThread must be either declared outside a class or be a static class member function. Being OO-type people who want to try to reduce namespace pollution we'll be using the static class member function approach, unfortunately this means that all of our class instances will be using the same function, which is exactly what I'm trying to avoid.

Racing for the Handle

By way of an introduction I'll start off with a brief description of what AfxBeginThread does. Essentially AfxBeginThread is a helper function that handles setting up and starting a CWinThread object for us. To do this it goes through (broadly speaking) four steps:

  1. Allocate a new CWinThread object on the heap.
  2. Call CWinThread::CreateThread() setting it to initially start suspended.
  3. Set the thread's priority.
  4. Call CWinThread::ResumeThread().

The default usage of CWinThread and AfxBeginThread has a slight problem. By default, as soon as the thread has completed it is deleted and so if our thread doesn't take much time to complete, by the time that AfxBeginThread returns our CWinThread pointer may already have been deleted and so dereferencing this pointer to get at the thread handle for a WaitForSingleObject() call will cause our application to crash.

Fortunately CWinThread provides a simple answer to this by way of the m_bAutoDelete member variable. As described above, this is TRUE by default. However, if we set it to FALSE, then the thread will not delete itself when it completes. This does have the same handle racing problem described above, because we can't access the member until AfxBeginThread returns, by which time it could already have been deleted! The answer is not to use the default AfxBeginThread and to create the thread initially suspended:

CWinThread* pThread = AfxBeginThread (ThreadFunc, lpVoid, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
ASSERT (NULL != pThread);
pThread->m_bAutoDelete = FALSE;
pThread->ResumeThread ();

Obviously, this does entail a little extra handling ourselves, but we're big boys and girls now aren't we!

The Solution

The solution to our problem isn't too complex and doesn't require too much work. Essentially, we are going to derive a class from CWinThread with a static member thread function, but this thread function will call a non-static member function to do the main guts of what our thread is supposed to do. It will also set the m_bAutoDelete member to FALSE in the constructor to overcome the handle racing problem.

Basically our class will look like this:

class CThreadEx : public CWinThread {
public:
	CMyThread (AFX_THREADPROC pfnThreadProc);
	static UINT ThreadFunc (LPVOID lpParam);
protected:
	virtual UINT Execute ();
};

CThreadEx::CThreadEx (AFX_THREADPROC pfnThreadProc) : CWinThread (pfnThreadProc, NULL) {
	m_bAutoDelete = FALSE;
	
	// Undocumented variable. Need to set the thread parameters variable to this
	m_pThreadParams = this;
}

// Static
UINT CThreadEx::ThreadFunc (LPVOID lpParam) {
	ASSERT (NULL != lpParam);
	CThreadEx* pThread = (CThreadEx*)lpParam;
	return pThread->Execute ();
}

UINT CThreadEx::Execute () {
	// Do something useful ...
	// Return a value to indicate success/failure
	return 0;
}	

Those of you with a keen eye will have noticed that the constructor for CThreadEx uses an initialiser list to call a CWinThread constructor that isn't documented. If you look in "thrdcore.cpp", you'll find a constructor that looks like this:

CWinThread::CWinThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam)
{
	m_pfnThreadProc = pfnThreadProc;
	m_pThreadParams = pParam;

	CommonConstruct();
}

This introduces us to two undocumented features, firstly the constructor itself, and secondly the member variables m_pfnThreadProc and m_pfnThreadParams. Also in "thrdcore.cpp" is the source for AfxBeginThread itself. The line below shows the use of the above constructor:

CWinThread* pThread = DEBUG_NEW CWinThread(pfnThreadProc, pParam);

This is a rather obscure ommission to have made, but we live and learn. The two parameters to this constructor are clearly the thread function and the parameter to be passed to the thread function. In the class outlined above the parameter to the thread function needs to be a pointer to the class instance, so our constructor sets m_pThreadParams = this.

Implementation

You have two options when it comes to implementing this solution. If you only require a one off solution, then use the above class as a template and build your functionality into it. If you think that this is something you'll be doing quite a lot, then define the above class as your base class and inherit from it putting your functionality in the derived class.

Using it all looks something like this:

CThreadEx* pThread = new CThreadEx (CThreadEx::ThreadFunc);
VERIFY (pThread->CreateThread());
...
WaitForSingleObject (pThread->m_hThread, INFINITE);
delete pThread;

The thread can be initially started suspended by specifying CREATE_SUSPENDED as the first parameter to the CreateThread() call if this functionality is required. Also, if you need to use a thread priority other than THREAD_PRIORITY_NORMAL just call SetThreadPriority() after CreateThread().

Conclusion

And there you have it ... a thread class that allows multiple instances to manipulate their own member data, etc. without having to have a separate thread function for each one. I hope this helps someone out there, it certainly helped me.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralCMyThread (AFX_THREADPROC pfnThreadProc); ?? can you help Pin
cfilorux26-Jul-06 12:05
cfilorux26-Jul-06 12:05 
GeneralRe: CMyThread (AFX_THREADPROC pfnThreadProc); ?? can you help Pin
vdrunen24-Oct-06 4:55
vdrunen24-Oct-06 4:55 
Generalthreads Pin
mehmetned17-Jul-06 23:02
mehmetned17-Jul-06 23:02 
GeneralAfxBeginThread Discussion Pin
Suburbank250020-Jun-03 8:53
Suburbank250020-Jun-03 8:53 
GeneralRe: AfxBeginThread Discussion Pin
MacDev20-Jun-03 9:16
MacDev20-Jun-03 9:16 
GeneralRe: AfxBeginThread Discussion Pin
pocjoc8-Dec-05 21:37
pocjoc8-Dec-05 21:37 
GeneralTerminating the Thread Pin
Zachary Randall28-Feb-03 5:29
Zachary Randall28-Feb-03 5:29 
GeneralRe: Terminating the Thread Pin
Member 10070019-Sep-04 8:29
Member 10070019-Sep-04 8:29 
GeneralThanks Pin
Christian Cheney31-Jan-03 6:20
Christian Cheney31-Jan-03 6:20 
GeneralUSING AFXBEGINTHREAD, UI IN A DLL Pin
22-Mar-02 5:04
suss22-Mar-02 5:04 
GeneralThank You Pin
14-Feb-02 14:21
suss14-Feb-02 14:21 
GeneralNo error int AfxBeginThread Pin
26-Jan-02 2:58
suss26-Jan-02 2:58 
GeneralGrammatical error in class declaration Pin
Krotow4-Sep-01 1:42
Krotow4-Sep-01 1:42 
GeneralRe: Grammatical error in class declaration Pin
Derek Lakin4-Sep-01 2:41
Derek Lakin4-Sep-01 2:41 
Generalproblem with compiling Pin
26-Aug-01 8:49
suss26-Aug-01 8:49 
GeneralRe: problem with compiling Pin
Derek Lakin27-Aug-01 21:36
Derek Lakin27-Aug-01 21:36 
GeneralThank you for your comments Pin
Derek Lakin6-Jun-01 3:41
Derek Lakin6-Jun-01 3:41 
GeneralNo problem to use AfxBeginThread Pin
Martin Richter [rMVP C++]6-Jun-01 3:13
Martin Richter [rMVP C++]6-Jun-01 3:13 
QuestionQuestions? Pin
skfnmuislædmufaæoi30-May-01 7:15
skfnmuislædmufaæoi30-May-01 7:15 
AnswerRe: Questions? Pin
Derek Lakin31-May-01 5:29
Derek Lakin31-May-01 5:29 
GeneralRe: Questions? Pin
Mike Klimentiev4-Jun-01 11:09
Mike Klimentiev4-Jun-01 11:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.