Thread questions...

Started by
10 comments, last by s_p_oneil 18 years, 10 months ago
I am creating two threads with CreateThread in a method... My question is what happens to the threads when the method ends and the scope where the thread was created becomes 'out of scope' if you see what I mean. Here's some psuedo to explain my situation...

void Interface::NextScreen()
{
   HANDLE Handle1 = CreateThread(yadda, yadda

   HANDLE Handle2 = CreateThread(yadda, yadda
}
// What happens to the threads when this method exits?

What I want to happen is just start these threads off running and then forget about them, is this going to happen or do I need to keep track of the threads in the interface object? Thanks for any help, and apologies if this doesn't make much sense, I am new to this area of C++. Mark Coleman
Advertisement
Once you create a thread, it just keep going and going and going and going. Until you pull the Duracell battery out and it dies. By Duracell battery, i mean:-

CloseHandle( hThreadHandle );
To extend rpgmaster's example, it is best to hold on to the handles for that. The threads will keep on going. I'd suggest a design much like:
class Interface{  HANDLE m_hThreadHandle;}void Interface::Create(){  m_hThreadHandle = CreatThread()}void Inteface::Destroy(){  CloseThread( m_hThreadHandle );}
Thanks rpg_code_master, so what are the implications of not closing these handles? Will the stack memory associated with them be lost?

Also, if the method that the thread is executing ends, what happens then? Is the thread automatigically closed or does it just linger?

Mark Coleman
You can read my answer on flipcode.
Fil (il genio)
Sorry guys, but these replies are not correct, and the way they are worded is mis-leading (including the reply on flipcode).

1) The thread will not keep running forever. It will stop when the thread function exits.

2) In some scenarios, it is perfectly reasonable to "fire and forget" when launching a worker thread. It depends on what you want it to do and what other threads it needs to interact with. For example, most simple HTTP servers create a new thread to handle each incoming HTTP request. The main thread doesn't track them, they just hande the request and shut down by themselves.

3) In light of points 1 and 2, mrmrcoleman's code is perfectly fine with one exception. If you don't close those thread handles, you have created a Windows resource leak. Windows has a finite number of handles, and some older versions of Windows can run out of handles fairly quickly.

4) The main reason to keep the thread handle is to be able to determine if the thread has exited. You just call WaitForSingleObject(hThread, 0). If it times out, then the thread is still running.

5) I would recommend that you create a simple worker thread class. This class will keep track of its own thread handle and close it at the right time. It could also have its own critical section and event objects for thread synchronization, queues for message passing, and a shutdown flag for properly shutting down threads that use infinite loops.

[Edited by - s_p_oneil on May 25, 2005 1:07:17 PM]
Here is one example of a simple Windows thread class. To use it, just derive from this class and override the virtual DoThreadTask() method. Debug() and Error() are logging functions defined elsewhere.

// Thread.h//#ifndef __Thread_h__#define __Thread_h__class CThread{protected:	char m_szName[256];	char m_szState[1024];	char m_szError[1024];	time_t m_nStateStartTime;	time_t m_nThreadStartTime;	time_t m_nThreadFinishTime;	bool m_bStarted;	bool m_bFinished;	bool m_bShutdown;	bool m_bPaused;	int m_nStackSize;	HANDLE m_hThread;public:	CThread(const char *pszName, int nStackSize = 65536)	{		strcpy(m_szName, pszName);		m_nStackSize = nStackSize;		*m_szState = 0;		*m_szError = 0;		m_bStarted = false;		m_bFinished = false;		m_bShutdown = false;		m_bPaused = false;		m_hThread = NULL;	}	virtual ~CThread()	{	}	const char *GetName()			{ return m_szName; }	const char *GetState()			{ return m_szState; }	const char *GetErrorMessage()	{ return m_szError; }	bool HasStarted()				{ return m_bStarted; }	bool HasFinished()				{ return m_bFinished; }	bool HasShutdown()				{ return m_bShutdown; }	bool HasFailed()				{ return *m_szError != 0; }	bool IsPaused()					{ return m_bPaused; }	bool Wait(int nMilliseconds)	{ return WaitForSingleObject(m_hThread, nMilliseconds) != WAIT_TIMEOUT; }	virtual void Pause()			{ m_bPaused = true; }	virtual void Resume()			{ m_bPaused = false; }	virtual bool Start()	{		m_hThread = (HANDLE)_beginthread(ThreadFunc, m_nStackSize, this);		if(m_hThread == (HANDLE)-1 || m_hThread == NULL)			return false;		return true;	}	virtual void Shutdown()	{		Debug("Shutting down thread: %s", m_szName);		m_bShutdown = true;	}	virtual void Fail(const char *pszError)	{		Error("Thread %s failed with error: %s", m_szName, pszError);		strncpy(m_szError, pszError, 1023);		m_szError[1023] = 0;		m_bShutdown = true;	}	void SetState(const char *pszState)	{		strncpy(m_szState, pszState, 1023);		m_szState[1023] = 0;		time(&m_nStateStartTime);	}	virtual void DoThreadTask() = 0;	static void ThreadFunc(void *pVoid)	{		CThread *pThread = (CThread *)pVoid;		Debug("Starting thread: %s", pThread->m_szName);		pThread->m_bStarted = true;		time(&pThread->m_nThreadStartTime);		try		{			pThread->DoThreadTask();		}		catch(...)		{			pThread->Fail("Unhandled exception (GPF)");		}		time(&pThread->m_nThreadFinishTime);		pThread->m_bFinished = true;		Debug("Exiting thread: %s", pThread->m_szName);	}};#endif // __Thread_h__


You might use it like this:

class CTestThread : public CThread{protected:public:	CTestThread(const char *pszName, int nStackSize = 65536) : CThread(pszName, nStackSize)	{	}	virtual void DoThreadTask()	{		while(!HasShutdown())		{			while(IsPaused() && !HasShutdown())			{				SetState("Paused");				Sleep(10);			}			SetState("Entering thread loop");						// Add your own code here.		}			}};


Your thread doesn't need to have an infinite loop, but any loop it can get stuck in for a while should check to see if the thread is supposed to pause or shut down. SetState() is only useful if you have threads getting hung somewhere and you aren't sure where. It keeps track of how long it's been since the last SetState() call, and you can add them like using printf or logging calls to figure it out. I wrote a threaded Java web crawler once, and it came in very handy for figuring out which network calls were hanging forever (and for which sites). You could also add a bit of code to it and use it to profile portions of your thread, though a general-purpose profiling class would be better for that.

[Edited by - s_p_oneil on May 25, 2005 3:31:35 PM]
Here's another example of a worker thread class. It is very old (the first worker thread class I ever created), so I consider it to be a poor design. However, I posted it because it has more functionality built into it than the other class, and I thought you might find bits of it useful. In particular, using the critical section macros to wrap Get/Set methods for thread-safe access to member variables.

Also, this class was designed specifically to process events. The worker thread calls WaitForEvent() to go to sleep when it has no events to process. When AddToInbox() is called, the worker thread is woken up to process new event(s). New events generated by the worker thread are put in the outbox. If I were to rewrite it today, I would use listener interfaces and allow blocks of events to be sent as well as individual events (to reduce the number of wait and wakeup calls). I would do a lot of other things differently, as well. Still, you may find it useful.

(If you don't want to use MFC and want to use helper classes like CCriticalSection and CEvent, just search for them in the MFC headers. They are completely inline, and easy to port into your own project.)

// WorkerThread.h : Header file//#ifndef __WORKERTHREAD_H#define __WORKERTHREAD_H#include <afxmt.h>#include <process.h>#ifndef THREAD_SAFE#define BEGIN_THREAD_SAFE       m_cs.Lock()#define END_THREAD_SAFE         m_cs.Unlock()#define THREAD_SAFE(operation)  m_cs.Lock(); operation; m_cs.Unlock()#endifclass CWorkerThread{protected:  // Fields for internal library and thread handling  HINSTANCE m_hLib;       // The library handle or application instance handle  HANDLE m_hThread;       // The worker thread's handle  HWND m_hWnd;            // A window handle to allow the worker thread to send messages to the GUI thread  bool m_bLoadLibrary;    // Set to true if this class loads a DLL  CCriticalSection m_cs;  // Critical section for making class thread-safe  CEvent m_event;         // Event for efficient thread synchronization  bool m_bShutdown;       // A shutdown flag, used to shut down the worker thread  bool m_bWaiting;        // A waiting flag, set to true when waiting for the event to be set  bool m_bContinueProcessingCheckPoint ;  //the name should help describe it  // Popular fields for passing information between threads  int m_nOperation;       // A hint to the worker thread telling it what to do  int m_nProgress;        // A progress indicator (i.e. number of percent of records handled so far)  HRESULT m_hError;       // A result code for the worker thread to set  CString m_strError;     // A string for the worker thread to set  CString m_strStatus;    // A string for the worker thread to set  CPtrList m_listInbox;   // A mechanism for passing objects to the worker thread  CPtrList m_listOutbox;  // A mechanism for receiving objects from the worker threadpublic:  CWorkerThread() : m_listInbox(1000), m_listOutbox(1000)  {    m_hLib = NULL;    m_hThread = NULL;    m_hWnd = NULL;    m_bLoadLibrary = false;    m_bShutdown = false;    m_bWaiting = false;    m_nOperation = 0;    m_nProgress = 0;    m_hError = NULL;    m_bContinueProcessingCheckPoint = true ;  }  ~CWorkerThread()  {    if(m_hThread || m_bLoadLibrary)    {      if(!ShutdownThread(300000))        TerminateThread();	  FreeLibrary();    }  }  // Starts the worker thread with a thread function pointer  bool StartThread(UINT (*pfnThreadFunc)(void *), bool bMFC=true, HWND hWnd=NULL)  {    if(!IsThreadRunning())    {      m_hThread = NULL;      m_hWnd = NULL;      m_bShutdown = false;      m_bWaiting = false;      m_nProgress = 0;      m_hError = NULL;      unsigned int dw;      if(bMFC)      {        CWinThread *pWinThread = AfxBeginThread((AFX_THREADPROC)pfnThreadFunc, (void *)this);        if(pWinThread)          m_hThread = pWinThread->m_hThread;      }      else        m_hThread = (HANDLE)_beginthreadex(NULL, 0, (UINT (WINAPI *)(void *))pfnThreadFunc, (void *)this, 0, &dw);      if(m_hThread)      {        m_hWnd = hWnd;        return true;      }    }    return false;  }  // Starts the worker thread with a library and function name  bool StartThread(const char *pszLibrary, const char *pszThreadFunc, bool bMFC=true, HWND hWnd=NULL)  {    if(!m_hLib)    {      char szPath[_MAX_PATH];#ifdef _DEBUG      sprintf(szPath, "%sD.DLL", pszLibrary);#else      sprintf(szPath, "%s.DLL", pszLibrary);#endif      m_hLib = LoadLibrary(szPath);      if(!m_hLib)        return false;      m_bLoadLibrary = true;    }    UINT (*pfnThreadFunc)(void *);    pfnThreadFunc = (UINT (*)(void *))GetProcAddress(m_hLib, pszThreadFunc);    if(!pfnThreadFunc)      return false;    return StartThread(pfnThreadFunc, bMFC, hWnd);  }  // Functions to handle shutting down the worker thread  bool GetShutdownFlag()          { THREAD_SAFE(register bool b = m_bShutdown); return b; }  void SetShutdownFlag(bool b)    { THREAD_SAFE(m_bShutdown = b); }  bool IsThreadRunning()          { THREAD_SAFE(register bool b = m_hThread && (WaitForSingleObject(m_hThread, 0) == WAIT_TIMEOUT)); return b; }  bool ShutdownThread(DWORD dwWait = INFINITE)  {    if(m_hThread)    {      SetShutdownFlag(true);      SendEvent();      if(WaitForSingleObject(m_hThread, dwWait) == WAIT_TIMEOUT)        return false;      m_hThread = NULL;    }    return true;  }  // WARNING: If ShutdownThread() fails because the worker thread is locked,  // you should call TerminateThread() before deleting this object!  bool TerminateThread(DWORD dwExitCode = -1)  {    if(!::TerminateThread(m_hThread, dwExitCode))      return false;    m_hThread = NULL;    return true;  }  void FreeLibrary()  {    if(m_bLoadLibrary)    {      ::FreeLibrary(m_hLib);      m_hLib = NULL;      m_bLoadLibrary = false;    }  }  // Functions to handle waiting for events  bool IsWaiting()                { THREAD_SAFE(register bool b = m_bWaiting); return b; }  void SendEvent()                { m_event.PulseEvent(); }  bool WaitForEvent(DWORD dw = INFINITE)  {    THREAD_SAFE(m_bWaiting = true);    bool bRetVal = (m_event.Lock(dw) != 0);    THREAD_SAFE(m_bWaiting = false);    return bRetVal;  }  // Function for safely loading a string resource without a CWinApp instance  // Note: This function requires the library handle to be set.  //       If running from an EXE, set the library handle to AfxGetInstanceHandle()  CString LoadStringResource(int nStringID)  {    char szBuffer[_MAX_PATH];    LoadString(m_hLib, nStringID, szBuffer, _MAX_PATH);    return CString(szBuffer);  }  // Function for sending a window message to the GUI thread  void SendMessage(UINT n, WPARAM w = 0, LPARAM l = 0)  {    if(m_hWnd)      ::SendMessage(m_hWnd, n, w, l);  }  // Set and Get functions (not thread-safe, must be set before StartThread() is called)  void SetOperation(int nOperation)     { m_nOperation = nOperation; }  int GetOperation()                    { return m_nOperation; }  void SetLibraryHandle(HINSTANCE hLib) { m_hLib = hLib; }  HINSTANCE GetLibraryHandle()          { return m_hLib; }  void SetThreadHandle(HANDLE hThread)  { m_hThread = hThread; }  HANDLE GetThreadHandle()              { return m_hThread; }  // Set and Get functions (thread-safe, call at any time)  void SetProgress(int n)           { THREAD_SAFE(m_nProgress = n); }  int GetProgress()                 { THREAD_SAFE(register int n = m_nProgress); return n; }  void SetErrorHandle(HRESULT h)    { THREAD_SAFE(m_hError = h); }  HRESULT GetErrorHandle()          { THREAD_SAFE(register HRESULT h = m_hError); return h; }  void SetErrorString(CString str)  { THREAD_SAFE(m_strError = str); }  CString GetErrorString()          { THREAD_SAFE(CString str = m_strError); return str; }  void SetStatusString(CString str) { THREAD_SAFE(m_strStatus = str); }  CString GetStatusString()         { THREAD_SAFE(CString str = m_strStatus); return str; }  // Functions for using the inbox and outbox  void AddToInbox(void *pRec)       { THREAD_SAFE(m_listInbox.AddTail(pRec));  SendEvent(); }  void AddToOutbox(void *pRec)      { THREAD_SAFE(m_listOutbox.AddTail(pRec)); SendEvent(); }  void *TakeFromInbox()             { THREAD_SAFE(register void *pVoid = m_listInbox.IsEmpty()  ? NULL : (char *)m_listInbox.RemoveHead());  return pVoid; }  void *TakeFromOutbox()            { THREAD_SAFE(register void *pVoid = m_listOutbox.IsEmpty() ? NULL : (char *)m_listOutbox.RemoveHead()); return pVoid; }  int InboxCount()                  { THREAD_SAFE(register int n = m_listInbox.GetCount());  return n; }  int OutboxCount()                 { THREAD_SAFE(register int n = m_listOutbox.GetCount()); return n; }  virtual int TotalCount()          { THREAD_SAFE(register int n = m_listInbox.GetCount() + m_listOutbox.GetCount()); return n; }  };#endif // __WORKERTHREAD_H


[Edited by - s_p_oneil on May 25, 2005 3:55:40 PM]
Use the [ source lang="c++" ] [ /source ] tags please!! (Without the spaces of course :P)

And shouldn't nStackSize = 65535? Due to an integer only being able to hold 65536 numbers *includeing* 0...

:D
Quote:Original post by rpg_code_master
Use the [ source lang="c++" ] [ /source ] tags please!! (Without the spaces of course :P)


Sorry, I just fixed them. I forgot it was [ source ], and used instead. Where is the link to the help page for these forums? I haven't seen it. It might help if I had bothered to spend more than 10 seconds looking for it. ;-) I used to just clik "Quote" to figure out what someone else did in their post, but they've started stripping the source code blocks when you quote someone.<br><br><!--QUOTE--><BLOCKQUOTE><span class="smallfont">Quote:</span><table border=0 cellpadding=4 cellspacing=0 width="95%"><tr><td class=quote><!--/QUOTE--><!--STARTQUOTE--><br>And shouldn't nStackSize = 65535? Due to an integer &#111;nly being able to hold 65536 numbers *includeing* 0...<br><br>:D<!--QUOTE--></td></tr></table></BLOCKQUOTE><!--/QUOTE--><!--ENDQUOTE--><br><br>No. A signed integer can hold up to 2.1 billion, and an unsigned integer up to 4.2 billion. You're thinking of an unsigned short. ;-) Of course, now that I think of it, I should make that parameter unsigned. If you set the stack size to 0, I believe it uses the default of 1MB, but most worker threads don't need that much. Keep in mind that 64K is very limiting, and I used that as a default because I was creating a bunch of very small threads. If you use that code, you might want to change the default for that function to 0 unless you're used to working with small stacks.<br><br>Of course, Windows is smart enough not to give you a full MB until you use it. I've created programs with hundreds of threads using the default, and it did not use up hundreds of MB. It would have if I had put a bunch of large objects &#111;n the stack (as local variables). Then again, if you try to do that with a 64K stack size, the program will just crash. ;-)<br>

This topic is closed to new replies.

Advertisement