_A C++ FRAMEWORK FOR DCE THREADS_ by Michael Yam Listing One /***** PTF.H -- Header file describes classes for pthread framework. *****/ #ifndef PTF_H #define PTF_H extern "C" { #include } #define TRUE 1 #define FALSE 0 /*--- CLASS: PTMutex. Description: Create, destroy, lock, unlock a mutex. ---*/ class PTMutex { public: PTMutex (); virtual ~PTMutex(); int lock(); int unlock(); int tryLock(); // dummy declarations to prevent copying by value. classes not having // pointer members may remove these declarations. Note: operator=() // should be chained in case of inheritance. PTMutex (const PTMutex &); PTMutex& operator=(PTMutex&); virtual const char *className() { return "PTMutex"; } virtual int isValid(); protected: pthread_mutex_t _hMutex; // handle to mutex private: int _validFlag; }; /*--- CLASS: PTCondVar. *--- Description: Manages condition variables and associated mutexes. ----*/ class PTCondVar : public PTMutex { public: PTCondVar (); // pthread_cond_init() virtual ~PTCondVar (); // pthread_cond_destroy() int signal (); int broadcast (); int timedWait (int seconds=0); pthread_cond_t hCondVar() { return _hCondVar; } // dummy declarations to prevent copying by value. // classes not having pointer members may remove these declarations. // Note: operator=() should be chained in case of inheritance. PTCondVar (const PTCondVar&); PTCondVar& operator=(PTCondVar&); const char *className() { return "PTCondVar"; } virtual int isValid(); protected: pthread_cond_t _hCondVar; // handle to condition variable private: int _validFlag; }; /*-- CLASS: PTObject. Description: Abstract class. Use to build a pthread. --*/ class PTObject { public: PTObject (); // use default schedule & policy virtual ~PTObject (); int start (); virtual int runPThread() = 0; // this gets called by start_routine() int tid () {return _tid;} int join (); // dummy declarations to prevent copying by value. // classes not having pointer members may remove these declarations. // Note: operator=() should be chained in case of inheritance. PTObject (const PTObject &); PTObject& operator=(const PTObject&); const char *className() { return "PTObject"; } virtual int isValid(); pthread_addr_t exitStatus; // thread's exit code (used by join()). protected: pthread_t _hPThread; // handle to thread // this static routine gets passed to pthread_create() static pthread_addr_t start_routine(void *obj); private: int _validFlag; int _tid; }; /*--- CLASS: PTSafeObject. Description: Derive from this to create a *--- thread-safe class. ----*/ class PTSafeObject { public: PTSafeObject (); ~PTSafeObject (); PTMutex *pPTMutex() const; // dummy declarations to prevent copying by value. // classes not having pointer members may remove these declarations. // Note: operator=() should be chained in case of inheritance. PTSafeObject (const PTSafeObject &); PTSafeObject& operator=(const PTSafeObject&); const char *className() { return "PTSafeObject"; } virtual int isValid(); protected: class PTLock { public: PTLock (PTSafeObject *ThreadSafe); ~PTLock (); private: PTMutex *_pPTLockMutex; }; private: // friend declaration needs to be here for nested classes. // might be an HP compiler bug. friend class PTLock; PTMutex *_pPTMutex; int _validFlag; }; #endif Listing Two /***** PTF.CPP -- Encapsulation of DCE PThreads. Classes include: PTObject, derive from this to create your threads; PTMutex, creates a mutex; PTCondVar, derived from PTMutex. Creates a condition variable and and an associated mutex; PTSafeObject, derive from this for classes which update shared resources; PTLock, locks a mutex. Works with PTSafeObject. Currently supports default thread creation: round-robin scheduling and medium priority. Currently supports default mutex creation: fast locks (as opposed to recursive and non-recursive locks). ***************************************************************/ #include "PTF.H" #include #include #ifndef NDEBUG #include #endif extern int errno; /*--- Function Name: PTObject::PTObject. Description: Constructor using *--- default thread scheduling (Round-robin) and priority (medium). *--- Returns: None ----*/ PTObject::PTObject() { _validFlag = FALSE; _tid = 0; // id assigned when thread is created exitStatus = 0; // initial thread return code _validFlag = TRUE; } /*--- Function Name: PTObject::~PTObject. Description: Destructor. Free *--- resources allocated to PThread. Returns: None ---*/ PTObject::~PTObject() { pthread_cancel (_hPThread); // issue a cancel message to thread pthread_detach (&_hPThread); // free resources of cancelled thread } /*---- Function Name: PTObject::isValid. Description: Return private variable *---- _validFlag. The variable indicates the state of the object, whether it *---- is valid or not. Returns: TRUE or FALSE ---*/ int PTObject::isValid() { return _validFlag; } /*--- Function Name: PTObject::join. Description: join() causes the calling *--- thread to wait for the thread object to complete. See pthread_join() in *--- DCE Dev. Ref. When the thread is complete, the thread's return code is *--- stored in a public variable: exitStatus. Returns: 0, success; -1, Error. *--- Check errno. ---*/ int PTObject::join () { pthread_t threadID = pthread_self(); int uniqueID = pthread_getunique_np (&threadID); if (uniqueID == tid()) { printf ("TID %d: Can't join thread to itself.\n", uniqueID); return -1; } return pthread_join (_hPThread, &exitStatus); } /*--- Function Name: PTObject::start. Description: Explicitly starts the *--- thread. Actually, thread creation is performed here as well. If thread *--- were created in the constructor, thread may start before a derived class *--- had a chance to complete its constuctor which would lead to *--- initialization problems. Returns: 0, success; -1, fail (errno = *--- EAGAIN || ENOMEM) ---*/ int PTObject::start() { // Create a thread using default schedule & priority. Also, pass in *this // ptr for argument to associate thread with an instance of this object. int status = pthread_create (&_hPThread, pthread_attr_default, (pthread_startroutine_t)&PTObject::start_routine, (void *)this); if (status == 0) _tid = pthread_getunique_np (&_hPThread); return status; } /*--- Function Name: PTObject::start_routine. Description: Static function is *--- passed into pthread_create. It is the start of the thread routine. In *--- turn, it calls the virtual function runThread() which is written by the *--- user of this class. Returns: None ---*/ pthread_addr_t PTObject::start_routine (void *obj) { // get object instance PTObject *threadObj = (PTObject *)obj; int status = threadObj->runPThread(); return (pthread_addr_t)status; } /*--- Function Name: PTMutex::PTMutex. Description: Constructor creates a *--- mutex with a default attribute (fast mutex). Returns: None ---*/ PTMutex::PTMutex() { _validFlag = FALSE; int status = pthread_mutex_init (&_hMutex, pthread_mutexattr_default); if (status == -1) return; _validFlag = TRUE; } /*--- Function Name: PTMutex::~PTMutex. Description: Destructor destroys mutex. *--- Assumes mutex is unlocked. DCE doesn't provide a direct way to determine *--- the state of a mutex. In case of failure, use assert() macro. *--- Returns: None ---*/ PTMutex::~PTMutex() { // assumes mutex is unlocked. DCE doesn't provide a direct way to determine // the state of a lock so I'll just try to destroy it without any checks. // I'm using a long name for the return value so that // "assert" macro is self documenting. int ipthread_mutex_destroy = pthread_mutex_destroy (&_hMutex); #ifndef NDEBUG pthread_t threadID = pthread_self(); int tid = pthread_getunique_np (&threadID); if (ipthread_mutex_destroy == -1) printf ("TID %d: Could not destroy mutex. errno=%d\n", tid, errno); assert (ipthread_mutex_destroy == 0); #endif } /*--- Function Name: PTMutex::isValid. Description: Used to determine if *--- object has been constructed successfully. Returns: TRUE or FALSE ---*/ int PTMutex::isValid() { return _validFlag; } /*--- Function Name: PTMutex::lock. Description: Lock this mutex. If mutex is *--- already locked, wait for it to become available. Returns: 0, success; *--- -1, fail (errno = EINVAL or EDEADLK) ---*/ int PTMutex::lock() { return pthread_mutex_lock (&_hMutex); } /*--- Function Name: PTMutex::trylock. Description: Try and lock this mutex. *--- If mutex is already locked, do not wait for it to become available. *--- Just return. Returns: 1, success; 0, mutex already locked; -1, fail, *--- mutex handle invalid ---*/ int PTMutex::tryLock() { return pthread_mutex_trylock (&_hMutex); } /*--- Function Name: PTMutex::unlock. Description: Unlock this mutex. *--- Returns: 0, success; -1, fail, invalid mutex handle ---*/ int PTMutex::unlock() { return pthread_mutex_unlock (&_hMutex); } /*--- Function Name: PTCondVar::PTCondVar. Description: Constructor creates a *--- condition variable. Returns: None ---*/ PTCondVar::PTCondVar() { _validFlag = FALSE; int status = pthread_cond_init (&_hCondVar, pthread_condattr_default); if (status == -1) return; // errno = EAGAIN or ENOMEM _validFlag = TRUE; return; } /*--- Function Name: PTCondVar::~PTCondVar. Description: Destructor destroy a *--- condition variable. It can fail if the condition variable is busy. *--- Returns: None --*/ PTCondVar::~PTCondVar() { int ipthread_cond_destroy = pthread_cond_destroy (&_hCondVar); #ifndef NDEBUG pthread_t threadID = pthread_self(); int tid = pthread_getunique_np (&threadID); if (ipthread_cond_destroy == -1) printf ("TID %d: Could not destroy condition variable. errno=%d\n", tid, errno); assert (ipthread_cond_destroy == 0); #endif } /*--- Function Name: PTCondVar::broadcast. Description: Wakes all threads *--- waiting on a condition variable object. Calling this routine means that *--- data is ready for a thread to work on. A broadcast lets one or more *--- threads proceed. Returns: 0, success; -1, fail (errno = EINVAL) ---*/ int PTCondVar::broadcast() { return pthread_cond_broadcast (&_hCondVar); } /*--- Function Name: PTCondVar::isValid. Description: Used to determine if *--- constructor succeeded or not. Returns: TRUE or FALSE ---*/ int PTCondVar::isValid() { return _validFlag; } /*--- Function Name: PTCondVar::signal. Description: Wakes one thread waiting *--- on a condition variable. Thread to wake is determined by its scheduling *--- policy. Returns: 0, on success; -1, on failure (errno = EINVAL) ---*/ int PTCondVar::signal() { return pthread_cond_signal (&_hCondVar); } /*--- Function Name: PTCondVar::timedWait. Description: Will wait on a *--- mutex/condition variable until thread receives a signal or broadcast, or *--- until specified number of seconds have elapsed, whichever comes first. *--- 0 seconds means wait forever. (default) Returns: 0, success. Thread was *--- signalled; 1, wait timed out. No signal; -1, wait failed. See errno ---*/ int PTCondVar::timedWait (int seconds) { int status; lock(); // lock this condition vars' mutex if (seconds <= 0) { // thread will wait here until it gets a signal // 0, default value, means wait forever. status = pthread_cond_wait (&_hCondVar, &_hMutex); } else { // wait for specified number of seconds // use non-portable dce routine to get absolute time from seconds. struct timespec delta; struct timespec abstime; delta.tv_sec = seconds; delta.tv_nsec = 0; // I'm using a long name for the return value so if "assert" // aborts, message is self-documenting. int ipthread_get_expiration_np = pthread_get_expiration_np (&delta, &abstime); assert (ipthread_get_expiration_np == 0); // thread will wait here until it gets a signal or // until abstime value is reached by system clock. status = pthread_cond_timedwait (&_hCondVar, &_hMutex, &abstime); if (status == -1 && errno == EAGAIN) status = 1; // lock timed-out } unlock(); // unlock internal mutex return status; } /*--- Function Name: PTSafeObject::PTSafeObject. Description: Used to make a *--- class thread-safe. Derive from this class, then add PTLock (this); to *--- the first line of each member function in your derived class that *--- accesses data. This class forms the outer part of a thread-safe class. *--- It creates and deletes a PTMutex object. The inner class (nested class *--- -- PTLock) locks and unlocks a PTMutex object. Returns: None ---*/ PTSafeObject::PTSafeObject() { _validFlag = FALSE; _pPTMutex = new PTMutex; if (!_pPTMutex->isValid()) return; _validFlag = TRUE; return; } /*--- Function Name: PTSafeObject::~PTSafeObject. Description: Delete internal *--- PTMutex object. Returns: None ---*/ PTSafeObject::~PTSafeObject() { delete _pPTMutex; } /*--- Function Name: PTSafeObject::pPTMutex. Description: Retrieve a copy of *--- the internal PTMutex object. Returns: None ---*/ PTMutex * PTSafeObject::pPTMutex() const { return _pPTMutex; } /*--- Function Name: PTSafeObject::isValid. Description: Determine if *--- constructor succeeded or not. Returns: TRUE or FALSE. ---*/ int PTSafeObject::isValid() { return _validFlag; } /*--- Function Name: PTSafeObject::PTLock::~PTSafeObject::PTLock. Description: *--- Destructor for class nested within PTSafeObject. It just unlocks the *--- PTMutex object. The outer class, PTSafeObject, deletes it. *--- Returns: None ---*/ PTSafeObject::PTLock::~PTLock() { (void)_pPTLockMutex->unlock(); } /*--- Function Name: PTSafeObject::PTLock::PTLock. Description: This class *--- forms inner (nested) class of a thread-safe object. The object is *--- responsible for locking and unlocking a PTMutex object. This constructor *--- locks it. A user should not instantiate this object explicitly. Pass *--- a "this" pointer to this function to give access to outer class' private *--- variables. The outer part (PTSafeObject) creates and deletes a PTMutex *--- object. Returns: None ---*/ PTSafeObject::PTLock::PTLock(PTSafeObject *ThreadSafe) { _pPTLockMutex = ThreadSafe->_pPTMutex; (void)_pPTLockMutex->lock(); } Listing Three /***** PTTF.H -- Template to create thread-safe types. *****/ /*--- TEMPLATE: PTSafeType. Description: This inherits from class PTSafeObject. *--- Implemented as template, it can make a variety of types threadsafe.---*/ #ifndef PTTF_H #define PTTF_H #include template class PTTSafeType : public PTSafeObject { public: void operator=(T threadSafeType) {set(threadSafeType);} operator T () {return get();} T operator ++(); T operator --(); T operator ++(T threadSafeType); T operator --(T threadSafeType); T get(); T set(T threadSafeType); private: T _threadSafeType; }; #ifdef RW_COMPILE_INSTANTIATE #include "PTTF.CC" #endif #endif Listing Four /**** PTTF.CC -- Template definition for PTTSafeType ****/ #ifndef PTTF_CC #define PTTF_CC /*--- Function Name: get. Description: retrieves a value safely in a threaded *--- environment. Note that the value is invalid if a set() has never been *--- called. Returns: value T. ---*/ template T PTTSafeType::get() { PTLock Lock(this); return _threadSafeType; } /*--- Function Name: set. Description: sets a value safely in a threaded *--- environment. Returns: previous value of T. ---*/ template T PTTSafeType::set (T threadSafeType) { PTLock Lock(this); T previous; previous = _threadSafeType; _threadSafeType = threadSafeType; return previous; } /*--- Function Name: prefix and postfix operators: ++ and --. Description: *--- Increment and decrement a value safely in a threaded environment *--- Returns: value of T after incrementing or decrementing. ---*/ template T PTTSafeType::operator ++() { PTLock Lock(this); return ++_threadSafeType; } template T PTTSafeType::operator --() { PTLock Lock(this); return --_threadSafeType; } template T PTTSafeType::operator ++(T threadSafeType) { PTLock Lock(this); return _threadSafeType++; } template T PTTSafeType::operator --(T threadSafeType) { PTLock Lock(this); return _threadSafeType--; } #endif Figure 1: Code fragment using PTLock to make your derived class thread-safe. derivedClass::updateData() { // Class is derived from PTSafeObject PTLock lock(this); // mutex locked // safe to update data here. ... // return invokes PTLock's destructor // which unlocks the mutex. } Figure 2. Code fragment illustrating thread-safe template. PTTSafeType sequenceNumber; ... sequenceNumber.set (0); // sets number to 0 safely. ++sequenceNumber; // inc number safely. // or sequenceNumber++; ... ====== /*-------------------------------------------------------------* File: TESTPTF.CPP Test program to exercise some of the PTF framework. Basically creates a boss thread which in turn creates 10 worker threads. Each worker thread prints hello world *------------------------------------------------------------*/ #include "PTF.H" #include #include extern errno; const int MaxThreads = 10; PTCondVar *gpCondMutex; /*----------------------------------------------------------- Class: WorkerThread Derived from PTObject. *-----------------------------------------------------------*/ class WorkerThread : public PTObject { public: WorkerThread(); virtual ~WorkerThread(); int runPThread(); // main thread routine int _callCounter; }; /*----------------------------------------------------------- WorkerThread Constructor *-----------------------------------------------------------*/ WorkerThread::WorkerThread() { // EMPTY } /*----------------------------------------------------------- WorkerThread Destructor *-----------------------------------------------------------*/ WorkerThread::~WorkerThread() { // EMPTY } /*----------------------------------------------------------- WorkerThread::runPThread() - main thread routine *-----------------------------------------------------------*/ int WorkerThread::runPThread() { // Don't start until we get a signal or until // 15 seconds has elapsed gpCondMutex->timedWait(15); fprintf (stdout, "TID %d: Hello World.\n", tid()); return 0; } /*----------------------------------------------------------- Class BossThread - this thread creates a bunch of worker threads. *-----------------------------------------------------------*/ class BossThread : public PTObject { public: BossThread(); ~BossThread(); int runPThread(); WorkerThread *threadArray[MaxThreads]; }; /*----------------------------------------------------------- BossThread::BossThread Create worker threads and store in array. *-----------------------------------------------------------*/ BossThread::BossThread() { for (int i=0; ijoin (); for (i=0; istart(); pthread_yield(); // give workers chance to start gpCondMutex->signal(); // only 1 worker thread should start join(); // try to join to self. PTF should let us proceed. return 7; // code used to test join() } /*----------------------------------------------------------- main() *-----------------------------------------------------------*/ int main (void) { gpCondMutex = new PTCondVar(); BossThread *boss = new BossThread(); boss->start(); // wait for boss thread to complete. if (boss->join () == -1) fprintf (stdout, "main(): Unable to join(). errno = %d\n", errno); else fprintf (stdout, "main(): Boss thread exit status = %d\n", boss->exitStatus); delete boss; delete gpCondMutex; } /*----------------------------------------------------------- End of TESTPTF.CPP *-----------------------------------------------------------*/