#ifndef LOCKINGPTR_H #define LOCKINGPTR_H ///////////////////////////////////////////////////////////////////// // lockingPtr.h - provides serialized access to class objects // // ver 1.3 // // Language: Visual C++, ver 6.0 // // Platform: Dell Dimension 8100, Win2000, SP 2 // // Application: CSE691 - Useful for projects #2 and #3 // // Author: Jim Fawcett, CST 2-187, Syracuse University // // (315) 443-3948, jfawcett@twcny.rr.com // ///////////////////////////////////////////////////////////////////// /* These classes follow closely those described by Andrei Alexandrescu in "Generic:volatile -- Multithreaded Programmer's Best Friend ... ", C/C++ Users Journal, Feb 2001. Class Operation =============== Objects of the Mutex class are used to serialize access to specific locations in the using code, e.g., code between calls to lock() and unlock(). If you need to serialize access to an object that can be accessed from several different locations in code, e.g., cout or a queue, then you want to use an object of the GlobalMutex class. An object of the safeOut class serializes access to a std::ostream, like cout or an output file stream. An object of the lockingPtr class provides serialized access to objects it references. It implements a smart pointer that uses an object of the Mutex or GlobalMutex classes to provide a lock mechanism. Public Interface for Mutex: =========================== Mutex mtx; // construct local Mutex object mtx.lock(); // acquire lock for this code region cout << "\n a serialized use of cout" << endl; mtx.unlock(); // release the lock Public Interface for GlobalMutex: ================================= GlobalMutex<1> gmtx; // declare once for each scope, use : // different integer parameter for : // each object you want to protect gmtx.lock(); // acquire a lock for cout object cout << "\n a serialized use of cout" << endl; gmtx.unlock(); // release the lock // Public Interface for SafeOut; ============================= safeOut out(cout); // creates a safeOut object for cout sout("...."); // uses safeOut::operator() sout << "..." << val << '\n'; // sends string, integer, and char Public Interface for lockingPtr: ================================ myObjType myObj; // some arbitrary object Mutex mtx; // provides a lock lockingPtr pMyObj(myObj,mtx); // serializes access pMyObj->memberFunction(args); // to myObj lockingPtr(*pMyObj,mtx)->memberFunction(args); // create, use temporary // lockingPtr This last statement is a very useful idiom. It causes the lockingPtr object to be constructed, used, and immediately destroyed. Thus, it provides a single locked access to an object with only one line of code. You may wish to use typedefs to shorten the lockingPtr declarations. See this module's test stub to see how to do that. */ /////////////////////////////////////////////////////////////// // build process // /////////////////////////////////////////////////////////////// // required files: // // lockingPtr.h, lockingPtr.cpp // // command line compile: // // cl /GX /GZ /DTEST_LOCKINGPTR lockingPtr.cpp // /////////////////////////////////////////////////////////////// /* Maintenance History: ==================== ver 1.3 : 28 Oct 2001 - fixed omission in GlobalMutex classes by added reference counting to ensure critical section is initialized and deleted just once. - added safeOut class - Modified lockingPtr class to use two template parameters. The first parameter represents the type of object being locked, the second represents the type of mutex used to lock. Now you can use both Mutex and GlobalMutex objects. ver 1.2 : 26 Oct 2001 - added GlobalMutex Class ver 1.1 : 23 Oct 2001 - added missing #include - added test stub and more detail to manual page - added annunciators used during lockingPtr test, e.g., when TEST_LOCKINGPTR is defined - removed unnecessary #include ver 1.0 : 10 Oct 2001 - first release */ // /* Design Notes: ============= These notes address the template GlobalMutex class. We address three issues: (1) the use of a static CRITICAL_SECTION, (2) the need for an integer template parameter, and (3) how, and where to declare objects of this type. First Issue: The GlobalMutex classes use a static CRITICAL_SECTION and a static integer refCount. One static CRITICAL_SECTION is shared by all instances of the class, due to the static qualifier. That means that you can declare a GlobalMutex<1> gmtx in any scope and always get the same CRITICAL_SECTION variable. Thus, you can lock a queue, that may be accessed by different threads from several locations, simply by declaring the gmtx object whereever you need the lock. There is one issue with that, however. You do not want to keep initializing and destroying the CRITICAL_SECTION variable every time you declare the object or it goes out of scope. So, this design uses a reference counting scheme to avoid that. The reference count starts at zero, and when a GlobalMutex<1> object is declared, its constructor initializes the CRITICAL_SECTION, but only if the reference count is zero. Then it increments the reference count. Therefore, it only gets initialized once. Anytime a GlobalMutex<1> object goes out of scope its reference count is decremented by its destructor. Only when the reference count is decremented to zero does the CRITICAL_SECTION get destroyed. Second Issue: Because we may need to lock more than one queue object (or whatever we lock) independently, we need a way to get more than one GlobalMutex class, since each class shares a single CRITICAL_SECTION. We can do that by templatizing the GlobalMutex class with an integer parameter: template class GlobalMutex { ... }; So, the declarations: GlobalMutex<1> gmtx1; GlobalMutex<2> gmtx2; generate objects from two different classes, and hence they do not share a common CRITICAL_SECTION, and so they lock independently. Third Issue: So, how and where do we declare objects of these classes. In order to properly initialize and destroy a GlobalMutex class's CRITICAL_SECTION, you need to declare an instance before you do any locking, that will stay in scope until after you're done with all your locking. One way to do that is to declare an instance near the beginning of the main routine. Then, anytime you need to lock an object, just declare an instance locally, to introduce the class and object name into that scope, and use it's lock() and unlock() member functions. The test stub shows you how to do that. */ // #include #include #include /////////////////////////////////////////////////////////////// // class declaration for Mutex // /////////////////////////////////////////////////////////////// class Mutex { public: Mutex(); ~Mutex(); void lock(); void unlock(); private: // different critical section for each Mutex object CRITICAL_SECTION cs; }; //----< default constructor >---------------------------------- inline Mutex::Mutex() { InitializeCriticalSection(&cs); } //----< destructor >------------------------------------------- inline Mutex::~Mutex() { DeleteCriticalSection(&cs); } //----< set lock >--------------------------------------------- inline void Mutex::lock() { EnterCriticalSection(&cs); } //----< remove lock >------------------------------------------ inline void Mutex::unlock() { LeaveCriticalSection(&cs); } // /////////////////////////////////////////////////////////////// // class declaration for GlobalMutex // /////////////////////////////////////////////////////////////// template class GlobalMutex { public: GlobalMutex(); ~GlobalMutex(); void lock(); void unlock(); private: // shared critical section for all GlobalMutex objects static CRITICAL_SECTION cs; static int refCount; }; //----< initialize static variables >-------------------------- template CRITICAL_SECTION GlobalMutex::cs; template int GlobalMutex::refCount = 0; //----< default constructor >---------------------------------- template GlobalMutex::GlobalMutex() { if(refCount++ == 0) InitializeCriticalSection(&cs); } //----< destructor >------------------------------------------- template GlobalMutex::~GlobalMutex() { if(--refCount == 0) DeleteCriticalSection(&cs); } //----< set lock >--------------------------------------------- template void GlobalMutex::lock() { EnterCriticalSection(&cs); } //----< remove lock >------------------------------------------ template void GlobalMutex::unlock() { LeaveCriticalSection(&cs); } // /////////////////////////////////////////////////////////////// // class declaration for lockingPtr // /////////////////////////////////////////////////////////////// template class lockingPtr { public: lockingPtr(volatile Obj &obj, Mtx &mtx); ~lockingPtr(); Obj& operator*(); Obj* operator->(); private: Obj* _pObj; Mtx *_pMtx; lockingPtr(const lockingPtr&); lockingPtr& operator=(const lockingPtr&); }; //----< constructor takes object to lock and Mutex >----------- template inline lockingPtr::lockingPtr(volatile Obj &obj, Mtx &mtx) : _pObj(const_cast(&obj)), _pMtx(&mtx) { #ifdef TEST_LOCKINGPTR cout << "\n constructing a LockingPtr object - calls lock()"; cout.flush(); #endif mtx.lock(); } //----< destructor >------------------------------------------- template inline lockingPtr::~lockingPtr() { _pMtx->unlock(); #ifdef TEST_LOCKINGPTR cout << "\n destroying a LockingPtr object - calls unlock()"; cout.flush(); #endif } // //----< get locked object >------------------------------------ template inline Obj& lockingPtr::operator*() { #ifdef TEST_LOCKINGPTR cout << "\n accessing contents of locked object"; cout.flush(); #endif return *_pObj; } //----< get access to locked object's members >---------------- template inline Obj* lockingPtr::operator->() { #ifdef TEST_LOCKINGPTR cout << "\n accessing member of locked object"; cout.flush(); #endif return _pObj; } // /////////////////////////////////////////////////////////////// // class declaration for safeOut // /////////////////////////////////////////////////////////////// class safeOut { public: safeOut(std::ostream &Out) : out(Out) { if(refCount++ == 0) InitializeCriticalSection(&cs); } ~safeOut() { if(--refCount == 0) DeleteCriticalSection(&cs); } void operator()(const std::string &msg) { EnterCriticalSection(&cs); out << msg.c_str(); out.flush(); LeaveCriticalSection(&cs); } void operator()(const char *msg) { EnterCriticalSection(&cs); out << msg; out.flush(); LeaveCriticalSection(&cs); } void operator()(int val) { EnterCriticalSection(&cs); out << val; out.flush(); LeaveCriticalSection(&cs); } void operator()(long val) { EnterCriticalSection(&cs); out << val; out.flush(); LeaveCriticalSection(&cs); } void operator()(char ch) { EnterCriticalSection(&cs); out << ch; out.flush(); LeaveCriticalSection(&cs); } safeOut& operator<<(const std::string &msg) { operator()(msg); return *this; } safeOut& operator<<(const char *msg) { operator()(msg); return *this; } safeOut& operator<<(int val) { operator()(val); return *this; } safeOut& operator<<(long val) { operator()(val); return *this; } safeOut& operator<<(char ch) { operator()(ch); return *this; } private: static CRITICAL_SECTION cs; static int refCount; std::ostream &out; }; #endif