#ifndef THREADS_H #define THREADS_H ///////////////////////////////////////////////////////////////////// // threads.h - class supporting creation of threads // // ver 3.7 // // Language: Visual C++, ver 6.0, SP 5 // // Platform: Dell Dimension 8100, Win 2000 Pro, SP 2 // // Application: CSE691/791 - SWM&A Demonstration // // Author: Jim Fawcett, CST 2-187, Syracuse Univ // // (315) 443-3948, jfawcett@twcny.rr.com // // // ///////////////////////////////////////////////////////////////////// /* Module Operations ================= The class thread encapsulates creation of Win32 threads. To construct a thread object, you will first need to create a class derived from the processing class, declared in this header file, and pass it to the thread constructor. Each derived processing class must provide a public run() function that defines the operations that the thread is expected to carry out. The test stub in the accompanying implementation file shows how to define classes derived from processing. This module also provides local locking via LLock and global locking via GLock, where i is a group that shares a common lock. Finally, the module provides a simple thread-safe blocking queue, based on the STL queue container adapter. Public Interface ================ class myProc : public processing { ... } myProc proc; // create a processing object thread t(proc); // create a thread object t.start(); // create running Win32 thread t.wait(); // wait for thread to complete t.sleep(50); // number of milliseconds to sleep t.canSuspend(false); // set false just before thread acquires lock t.canSuspend(true); // set true just after thread releases lock t.suspend(); // suspend thread operation t.resume(); // resume thread operation HANDLE th = t.handle(); // return Win32 thread handle unsigned int id = thread::currentThread(); // return current thread's ID LLock l; // local lock that protects holder l.lock(); // set lock l.unlock(); // unset lock // Global lock protects holder of any GLock<1> GLock<1> l1; // instance. GLock<-1> reserved for syncOut. l1.lock(); // set lock l1.unlock(); // unset lock sout << "a string"; // globally available synchronized output sout << locker << "a string" << 3.1415927 << myObject << unlocker; // locker & unlocker are manipulators needed // to protect a sequence of insertions BQueue Q // create blocking queue holding std::strings Q.enQ("an item"); // push onto queue std::string str = Q.deQ(); // pop from queue size_t s = Q.size(); // number of elements in queue Q.clear() // remove all contents from queue */ // /////////////////////////////////////////////////////////////////////// // Build Process // /////////////////////////////////////////////////////////////////////// // Required Files: // // threads.h, threads.cpp // // // // Compiler Command: // // cl /GX /DTEST_THREADS threads.cpp // // // /////////////////////////////////////////////////////////////////////// /* Maintenance History =================== ver 3.7 : 08 May 2004 - fixed a subtle bug in the blocking queue. Added a second check to the deQ locking test. See deQ prologue for details. ver 3.6 : 01 May 2004 - fixed blocking queue, BQueue::clear() function which called a non-existing std::queue function, discovered by Arun Iyer. - added copy ctors and assignment operators to LLock and BQueue classes. ver 3.5 : 27 Apr 2004 - added a static function currentThread() to return thread id ver 3.4 : 12 Apr 2004 - added inline qualifier to synchOut constructor - EVERY definition in a header file MUST be either template or inline. The omission was an oversight. ver 3.3 : 10 Apr 2004 - a pure virtual clone() function has been added to the processing interface so that each thread can make its own copy of the proc. That avoids a problem when the derived proc object is declared inside a loop, causing each prior proc to get blown away. That could be avoided in ver 3.2 by declaring the proc instance above the loop. Now that is not necessary. ver 3.2 : 08 Apr 2004 - a few cosmetic changes, but no functional changes ver 3.1 : 08 Apr 2004 - added syncOut, sout, and locker, unlocker manipulators after first upload. - added blocking to the Queue class, renaming it BQueue. The code provides a nice example of how to use Win32 events. ver 3.0 : 08 Apr 2004 - separated this class from the mtqueue and lockingPtr code to simplify things. This module now has a tiny queue and lock classes. - added willSelfDestruct() function which you call if, and only if, you created a thread on the heap. The thread then deletes itself when it returns from its processing::run() function. You will find a demonstration in the test stub for this module. - changed the way handles are closed in the start() function to avoid an irritating exception thrown only during debugging. ver 2.1 : 18 Nov 2001 - added setPriority() and alive() member functions ver 2.0 : 10 Nov 2001 - added procWithQ class, providing queuing services for its thread ver 1,2 : 05 Nov 2001 - made processing class abstract - provided fixes to the manual page ver 1.1 : 28 Oct 2001 - added missing file names to Build Process comments ver 1.0 : 26 Oct 2001 - first release */ // /* Planned Modifications ===================== - add waitAll() function using a static count of all currently running threads */ #include #include #include #include ///////////////////////////////////////////////////////////////////// // thread processing interface declaration class processing { public: virtual ~processing() {} virtual processing* clone()=0; // used by thread to make its own copy of derived proc instance // which provides the thread's application specific processing virtual void run()=0; }; ///////////////////////////////////////////////////////////////////// // thread class declaration class thread { public: enum priority { low, normal, high }; thread(processing &proc); virtual ~thread(); void start(); void wait(); static unsigned int currentThread(); HANDLE handle(); void canSuspend(bool b=true); // set false if thread holds a lock bool suspend(); // otherwise thread may deadlock void resume(); void sleep(int MilliSec); void setPriority(priority pr); bool alive(); void willSelfDestruct(bool b=true); // call this only if thread created // on heap protected: processing* _pProc; HANDLE th; unsigned int threadID; static unsigned int __stdcall threadProc(void *pSelf); bool _canSuspend; bool _alive; bool _suicidal; }; // //----< constructor >------------------------------------------------ inline thread::thread(processing &proc) : _pProc(proc.clone()), _suicidal(false), _canSuspend(true), th(0) { } //----< enable suicide - call this only if created on heap >--------- inline void thread::willSelfDestruct(bool b) { _suicidal = b; } //----< return thread handle >--------------------------------------- inline HANDLE thread::handle() { return th; } //----< get current thread id >-------------------------------------- inline unsigned int thread::currentThread() { return ::GetCurrentThreadId(); } //----< decrement suspend count >------------------------------------ // // if count is zero, make thread schedualable // inline void thread::resume() { ::ResumeThread(th); } //----< put thread to sleep for t milliseconds >--------------------- inline void thread::sleep(int t) { ::Sleep(t); } //----< is thread locked? >------------------------------------------ inline void thread::canSuspend(bool b) { _canSuspend = b; } // ///////////////////////////////////////////////////////////////////// // LLock - local locking class declaration class LLock { public: LLock(); LLock(const LLock& ll); ~LLock(); LLock& operator=(const LLock& ll); void lock(); void unlock(); private: CRITICAL_SECTION cs; }; //----< constructor >------------------------------------------------ inline LLock::LLock() { InitializeCriticalSection(&cs); } //----< copy constructor >------------------------------------------- inline LLock::LLock(const LLock& ll) { InitializeCriticalSection(&cs); // intentionally same as void ctor } //----< destructor >------------------------------------------------- inline LLock::~LLock() { DeleteCriticalSection(&cs); } //----< assignment >------------------------------------------------- inline LLock& LLock::operator =(const LLock& ll) { return *this; // do nothing } //----< set lock >--------------------------------------------------- inline void LLock::lock() { EnterCriticalSection(&cs); } //----< unset lock >------------------------------------------------- inline void LLock::unlock() { LeaveCriticalSection(&cs); } // ///////////////////////////////////////////////////////////////////// // GLock - global locking class declaration // Please reserve GLock<-1> for use by syncOut template class GLock { public: GLock(); ~GLock(); void lock(); void unlock(); private: // shared critical section for all GLock objects static CRITICAL_SECTION cs; static long refCount; }; //----< initialize static variables >-------------------------- template CRITICAL_SECTION GLock::cs; template long GLock::refCount = 0; //----< default constructor >---------------------------------- template GLock::GLock() { if(refCount == 0) InitializeCriticalSection(&cs); InterlockedIncrement(&refCount); } //----< destructor >------------------------------------------- template GLock::~GLock() { InterlockedDecrement(&refCount); if(refCount == 0) DeleteCriticalSection(&cs); } //----< set lock >--------------------------------------------- template void GLock::lock() { EnterCriticalSection(&cs); } //----< remove lock >------------------------------------------ template void GLock::unlock() { LeaveCriticalSection(&cs); } // ///////////////////////////////////////////////////////////////////// // BQueue class declaration - blocking queue based on STL queue template class BQueue { public: BQueue(); BQueue(const BQueue& bq); ~BQueue(); BQueue& operator=(const BQueue& Q); void enQ(const T& t); T deQ(); void clear(); size_t size(); private: std::queue _Q; LLock _l; HANDLE _hEvent; bool _waitFlag; }; //----< constructor >------------------------------------------------ template BQueue::BQueue() : _waitFlag(false) { _hEvent = CreateEvent(0,FALSE,TRUE,0); if(_hEvent==NULL) throw exception("CreateEvent failed in BQueue::BQueue()"); } //----< copy constructor >------------------------------------------- template BQueue::BQueue(const BQueue& bq) : _Q(bq._Q), _waitFlag(false) { _hEvent = CreateEvent(0,FALSE,TRUE,0); if(_hEvent==NULL) throw exception("CreateEvent failed in BQueue::BQueue()"); } //----< destructor >------------------------------------------------- template BQueue::~BQueue() { CloseHandle(_hEvent); } //----< assignment >------------------------------------------------- template BQueue& BQueue::operator =(const BQueue& bq) { if(this==&bq) return *this; _Q = bq._Q; _waitFlag = false; SetEvent(_hEvent); return *this; } // //----< enqueue >---------------------------------------------------- template void BQueue::enQ(const T& t) { _l.lock(); _Q.push(t); SetEvent(_hEvent); _l.unlock(); } //----< dequeue >---------------------------------------------------- // // This looks more complicated than you might think necessary. // However, without the second _Q.size() check: // If a single item is in the queue and a thread moves toward // the deQ, but finishes its time slice before deQ'ing, then // another thread may get through the locks, deQ the single // item and leave. When the first thread wakes up, it has // a false _waitFlag and so attempts to deQ the empty queue. // The second check prevents this. Now, if the second check // fails the deQ attempt must be repeated so we don't return // an uninitialized t value. // template T BQueue::deQ() { bool _waitFlag = false; bool popped = false; T t; do { _l.lock(); if(_Q.size() == 0) { ResetEvent(_hEvent); _waitFlag = true; } _l.unlock(); if(_waitFlag) { // sout << "\n Blocking on empty Q"; // to see blocking action WaitForSingleObject(_hEvent,INFINITE); } _waitFlag = false; _l.lock(); if(_Q.size() > 0) { t = _Q.front(); _Q.pop(); popped = true; } _l.unlock(); } while(!popped); return t; } // //----< purge queue >------------------------------------------------ template void BQueue::clear() { while(_Q.size() > 0) _Q.pop(); } //----< return number of elements in queue >------------------------- template size_t BQueue::size() { return _Q.size(); } // ///////////////////////////////////////////////////////////////////// // syncOut class class syncOut { public: syncOut(std::ostream& out); template syncOut& operator<<(const T& t); syncOut& operator<<(syncOut& (*op)(syncOut&)); template void operator()(const T& t); private: GLock<-1> _l; std::ostream& _out; }; //----< constructor >------------------------------------------------ inline syncOut::syncOut(std::ostream& out) : _out(out) {} //----< insertion >-------------------------------------------------- template syncOut& syncOut::operator<<(const T& t) { _l.lock(); _out << t; _out.flush(); _l.unlock(); return *this; } //----< insertion >-------------------------------------------------- inline syncOut& syncOut::operator<<(syncOut& (*op)(syncOut&)) { return (*op)(*this); } //----< functor >---------------------------------------------------- template void syncOut::operator()(const T& t) { _l.lock(); _out << t; _out.flush(); _l.unlock(); } // //----< lock manipulator >------------------------------------------- inline syncOut& locker(syncOut& so) { GLock<-1>().lock(); return so; } //----< unlock manipulator >----------------------------------------- inline syncOut& unlocker(syncOut& so) { GLock<-1>().unlock(); return so; } extern syncOut sout; #endif