Boost logo

Boost :

From: Borgerding, Mark A. (MarkAB_at_[hidden])
Date: 2000-06-06 09:19:17


FWIW, here is the basic interface and (more importantly) the design &
rationale behind the interface of a thread base class I have used.

I have edited some of the most Win32-specific things out of it, but it still
has that flavor. Apologies to purists. The concepts should translate fairly
well (e.g. Start==pthread_create, WaitForExit==pthread_join, DWORD=unsigned
long).

highlights:
        -abstract class BaseThread, two pure virtual functions:
                unsigned int Run(); void Stop()
        rationale: KISS, require only the two most basic things that should
be requested of a thread. True, there may be threads that do not require a
Stop, but it is better practice IMO to always have one. Other thread
behavior might be provided by virtual functions.

        -two-step thread creation: allocate a subclass of BaseThread and
then call Start()
        rationale: allows the subclass constructor to take any sort of
arguments and perform any initialization.

        -uses intrusive reference count, AddRef/Release
        rationale: A simple delete could lead to race conditions. On the
other hand, obtaining a smart pointer from the base class would require
downcasting to get it to a smart pointer to the subclass. The intrusive
approach might be made more palatable by a smart-pointer class for
intrusive-rc objects similar to the discussions on boost earlier this year.

        -constructor and copy constructor are protected, assignment is
private.
        rationale:BaseThread contains only members that pertain to the state
of the *thread*, not the task that is being performed in that thread. The
state of the task is to be held in the subclass. The copy constructor is
functionally identical to a void constructor, since thread state information
should not be copied from one object to another. The constructed thread
must still be "Start"ed. The assignment operator is made private because
the two objects involved in the assignment operator may be Started or not.

        -uses virtual protected destructor (I tried private, but this seemed
to silently override the virtual part -- could just be MSVC).
Unfortunately, this does not prevent a subclass from making its d'tor public
and circumventing the base class protected d'tor. This is also the reason
for the OnDestroy method. Any alternatives?
        rationale: I wanted to enforce heap allocation of the object. But
considering the hurdles that this approach brings, perhaps this should not
be a requirement.

        -I waffle back and forth about catching exceptions that come out of
Run(). On the positive, processes may continue even if one of the thread
dies. On the negative, the thread that dies may be crucial to correct
operation of the process and calling terminate() may be a better alternative
than running without that thread. Forcing the subclass to handle the
exceptions should (hopefully) cause the designer of the subclass to consider
the effect of exceptions.

class CBaseThread
{
public:
   bool Start() throw();

   virtual void Stop() = 0; // request a stop at the thread's earliest
opportunity

   bool WaitForExit(DWORD msToWait= INFINITE,DWORD * pReturnCode = NULL )
throw();
   // Wait for the thread to exit. Make sure you have called Stop(), if
required.

   bool Terminate() throw();
   // ugly kill, no cleanup is done. Please try Stop first.
   // This should only be used when a thread stubbornly refuses to stop,
e.g. deadlocked

   // reference counted deletion of the object
   void AddRef() throw();// called once on constructor, you should call it
too if you want to copy the pointer
   void Release() throw();// when the last reference is gone, the object
will be deleted

   bool SetPriority(int nPriority);
   int GetPriority() const;

   DWORD GetThreadId() const throw();// zero if not running
protected:
   CBaseThread();
   CBaseThread(const CBaseThread & other);

   virtual ~CBaseThread();
   // If you override this, please do not make it public.
   // The only reason this d'tor is not private is because the
   // compiler ignores the virtual keyword if it is private -- thus
   // the subclass' auto-generated destructor would not get called.
   // As a general rule for this, subclasses should take care of
   // any cleanup in the OnDestroy() method.

private:
   friend DWORD WINAPI BaseThreadRunProc(LPVOID);
   // actually runs this->Run()

   CBaseThread & operator =(const CBaseThread & other) throw();// copy
construction might be useful for a subclass, but I doubt assignment will be
required

   virtual void OnDestroy() throw ();
   // This gets called just before he private destructor is called.
   // Subclasses should override this to clean up their object

   virtual DWORD Run() throw()= 0;
   // Run() should execute as long as the thread should stay active or until
Stop is called,
   // whichever comes first. It should NEVER allow an exception to travel
out of it.

   LONG m_refs;// reference count
   HANDLE m_hThread;// handle to the currently running thread
   DWORD m_exitCode;
   DWORD m_threadId;
};


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk