/******************************************************************** created: 2005/01/07 created: 7:1:2002 13:08 filename: D:\public\base\AutoLock.h file path: D:\public\base file base: AutoLock file ext: h author: Günter Baumgart purpose: *********************************************************************/ #pragma once #include // Required for ATLASSERT macro #include namespace AutoLock { // SWMR enum wait lock types enum _LOCKTYPE { LT_WAITTOREAD = 0, LT_WAITTOWRITE = 1 } ; template class CAutoLockT { // Attributes private: T* m_pObject; // the locked object // Ctor/dtor public: // Critical Section Ctor inline CAutoLockT( T* pObject ) throw() : m_pObject(pObject) { ATLASSERT( NULL != pObject ); m_pObject->Lock(); } // Mutex Ctor inline CAutoLockT( T* pObject, DWORD dwTimeout ) : m_pObject(pObject) { ATLASSERT( NULL != pObject ); m_pObject->Lock( dwTimeout ); } // SRMR Ctor inline CAutoLockT( T* pObject, const _LOCKTYPE uLockType, LPCTSTR szOperation = NULL ) throw() : m_pObject(pObject) { ATLASSERT( NULL != pObject ); m_pObject->Lock( uLockType, szOperation ); } // dtor inline ~CAutoLockT() { m_pObject->Unlock(); } }; //+---------------------------------------------------------------------------- // Class: CAutoLockWaitExc // // Purpose: CLockableMutex::Lock() method will throw this exception when // an error occurs. // // Comment: NOTE: Callers should catch this exception. //+---------------------------------------------------------------------------- class CAutoLockWaitExc { // Ctor public: CAutoLockWaitExc( DWORD dwWaitResult, DWORD dwError ) : m_dwWaitResult( dwWaitResult ) , m_dwError( dwError ) {} // Public Accessors public: inline const DWORD GetWaitResult() { return m_dwWaitResult; }; inline const DWORD GetLastError() { return m_dwError; }; // Attributes private: DWORD m_dwWaitResult; // WaitForSingleObject return DWORD m_dwError; // GetLastError return code }; //+---------------------------------------------------------------------------- // Class: CAutoLockExc // // Purpose: CLockableMutex::Lock() method will throw this exception when // an error occurs. // // Comment: NOTE: Callers should catch this exception. //+---------------------------------------------------------------------------- class CAutoLockExc { // Ctor public: CAutoLockExc( DWORD dwError ) : m_dwError( dwError ) {} // Public Accessors public: inline const DWORD GetLastError() { return m_dwError; }; // Attributes private: DWORD m_dwError; // GetLastError return code }; //+---------------------------------------------------------------------------- // Embedded Class: CAutoLockTimeoutExc // // Purpose: CLockableMutex::Lock() method will throw this timeout exception // when the timeout period has been exceeded. // // Comment: NOTE: Callers should catch this exception. //+---------------------------------------------------------------------------- class CAutoLockTimeoutExc { // Ctor public: CAutoLockTimeoutExc( DWORD dwTimeout ) : m_dwTimeout( dwTimeout ) {} // Public Accessors public: inline const DWORD GetTimeout() { return m_dwTimeout; }; // Attributes private: DWORD m_dwTimeout; // Timeout value (although passed in // to the Lock method, we store it // here and make it available in the // exception for convenience }; //+---------------------------------------------------------------------------- // Class: CLockableCS // // Purpose: This class is intended to be used as a base class for a // multithreaded Critical Section lockable object. // //+---------------------------------------------------------------------------- class CLockableCS { // Methods public: // Ctor / Dtor inline CLockableCS() throw() { InitializeCriticalSection( &m_CS ); } inline ~CLockableCS() throw() { DeleteCriticalSection( &m_CS ); } // Lock / Unlock inline void Lock() throw() { EnterCriticalSection( &m_CS ); } inline void Unlock() throw() { LeaveCriticalSection( &m_CS ); } // Attributes private: CRITICAL_SECTION m_CS; // Internal Critical Section }; //+---------------------------------------------------------------------------- // Class: CLockableMutex // // Purpose: This class is intended to be used as a base class for a // Mutex lockable object. // // Comment: NOTE: The lock method for this class will throw a // CMutexLockExc exception (see below for declaration). // Callees should catch this exception when calling the Lock method. // // Scope: Single or Multiple Processes //+---------------------------------------------------------------------------- class CLockableMutex { public: inline CLockableMutex( LPCTSTR szName ) //throw() : m_hMutex( ::CreateMutex( NULL, FALSE, szName ) ) , m_bAlreadyExists( FALSE ) { // If the call has succeeded, check if the named mutex has already existed if( NULL != m_hMutex ) { m_bAlreadyExists = ( ERROR_ALREADY_EXISTS == ::GetLastError( ) ); } else { // The call has failed. if( ERROR_ACCESS_DENIED == ::GetLastError( ) ) { // We'll get the Access denied error when the mutex // exists and the user does not have sufficient permissions // to create an existing mutex (as in above). // So we retry using OpenMutex if( NULL == ( m_hMutex = ::OpenMutex( MUTEX_MODIFY_STATE, FALSE, szName ) )) { // OpenMutex failed also, so throw an exception throw CAutoLockExc( ::GetLastError( ) ); } else { m_bAlreadyExists = TRUE; } } else { // Other unknown error throw CAutoLockExc( ::GetLastError( ) ); } } } inline ~CLockableMutex() throw() { if(m_hMutex) ::CloseHandle( m_hMutex ); } //+------------------------------------------------------------------- // Method: AlreadyExists // Purpose: Use this method when you want to use a mutex to limit // an executable to a single instance. Declare a CLockableMutex // variable and check this param to find out if it existed // prior to instantiating this class. // // Params: void // Return: void // // Comments: See msdn docs on CreateMutex. This //+------------------------------------------------------------------- const BOOL AlreadyExists() { return m_bAlreadyExists; }; //+------------------------------------------------------------------- // Method: Lock // Purpose: Used by the CAutoLockT class to limit inter-process // access. // // Params: ms Timeout // Return: void // // Comments: Throws CTimeoutException or CException exceptions //+------------------------------------------------------------------- inline void Lock( DWORD dwTimeout ) { DWORD dwWaitResult = 0; dwWaitResult = ::WaitForSingleObject( m_hMutex, dwTimeout ); switch( dwWaitResult ) { case WAIT_OBJECT_0: break; case WAIT_TIMEOUT: throw CAutoLockTimeoutExc( dwTimeout ); break; default: throw CAutoLockWaitExc( dwWaitResult, ::GetLastError() ); } } //+------------------------------------------------------------------- // Method: Unlock // Purpose: Used by the CAutoLockT class (CAutoLockT::dtor) to // release the mutex // // Params: void // Return: void inline void Unlock() throw() { if(m_hMutex) { ::ReleaseMutex( m_hMutex ); } } private: HANDLE m_hMutex; // Name Mutex Handle BOOL m_bAlreadyExists; // TRUE if named mutex already exists }; //+---------------------------------------------------------------------------- // Class: CLockableSWMR // // Purpose: This class is intended to be used as a base class for a // SWMR (Single Writer, Multiple Reader) lockable object. // // Acknowledgements: // This class is base on work by Jeffrey Richter in his book // "Programming Applications for Microsoft Windows" // // Usage: // Create a class derived from this and another resource class (such as // an std::list<> class) when you have a scenario where the list seldom // gets updated, but is frequently read by multiple threads. Under this // scenario, this class would be more efficient than the CLockableCS // class because multiple threads could read simultaneously whereas a // CLockableCS implementation would only allow a single thread to read // or write. Conversely, because this class makes use of the kernel // semaphore object, it is not as efficient a CLockableCS under single // read thread scenarios. // // To Lock to Read: // CAutoLock< CLockableSWMR_Derived > lock( &m_SWMR, LT_WAITTOREAD ); // // To Lock to Write: // CAutoLock< CLockableSWMR_Derived > lock( &m_SWMR, LT_WAITTOWRITE ); // // Public Methods: // Lock( enumLockType, ... ) // Unlock() // // Private Methods: // WaitToRead(...) // WaitToWrite(...) // Scope: Single Process //+---------------------------------------------------------------------------- class CLockableSWMR { public: CLockableSWMR( ) throw() : m_hSemReaders( CreateSemaphore(NULL, 0, MAXLONG, NULL) ) , m_hSemWriters( CreateSemaphore(NULL, 0, MAXLONG, NULL) ) , m_nWaitingReaders( 0 ) , m_nWaitingWriters( 0 ) , m_nActive( 0 ) , m_sName( _T("") ) { } CLockableSWMR( LPCTSTR szName ) throw() : m_hSemReaders( CreateSemaphore(NULL, 0, MAXLONG, NULL) ) , m_hSemWriters( CreateSemaphore(NULL, 0, MAXLONG, NULL) ) , m_nWaitingReaders( 0 ) , m_nWaitingWriters( 0 ) , m_nActive( 0 ) , m_sName( szName ) { } ~CLockableSWMR() throw() { m_nWaitingReaders = 0; m_nWaitingWriters = 0; m_nActive = 0; CloseHandle(m_hSemReaders); CloseHandle(m_hSemWriters); } // Attributes private: CLockableCS m_InternalCS; // Ensures exclusive access // to member variables HANDLE m_hSemReaders; // Readers wait on if a writer has access HANDLE m_hSemWriters; // Writers wait on if a reader has access int m_nWaitingReaders; // # of readers waiting int m_nWaitingWriters; // # of writers waiting int m_nActive; // # of threads currently w/access // (0 == no threads, // >0 == # of readers, // -1 == 1 writer) CString m_sName; // Debug diagnostic string // Thread tracking enum enum _ACTIVETHREADS { AT_ONEWRITER = -1, AT_NONE = 0 }; // Operations public: //+------------------------------------------------------------------- // Method: Lock // Purpose: Used by the CAutoLockT class to provide thread safe access. // // Params: enumLockType, szOperation (debug operation string) // Return: void //+------------------------------------------------------------------- void Lock(const _LOCKTYPE enumLockType, LPCTSTR szOperation = NULL ) throw() { switch(enumLockType) { case LT_WAITTOREAD: WaitToRead( szOperation ); break; case LT_WAITTOWRITE: WaitToWrite( szOperation ); break; default: ATLASSERT(0); } } //+------------------------------------------------------------------- // Method: Unlock // Purpose: Used by the CAutoLockT class (CAutoLockT::dtor) to // release the CS // // Params: void // Return: void //+------------------------------------------------------------------- void Unlock() throw() { HANDLE hSem = NULL; // Assume no threads are waiting long lCount = 1; // Assume only 1 waiter wakes; // always true for writers { // Lock scope // Ensure exclusive access to member variables CAutoLockT< CLockableCS > lock( &m_InternalCS ); ATLTRACE( _T("\t\t%s: Unlock - m_nActive: %d"), m_sName, m_nActive); if (m_nActive > 0) { // Readers have control so a reader must be done m_nActive--; } else { // Writers have control so a writer must be done m_nActive++; } ATLTRACE( _T("\t\t%s: Unlock - m_nActive: %d\n"), m_sName, m_nActive); if(m_nActive == 0) { // No thread has access, who should wake up? // NOTE: It is possible that readers could never get access // if there are always writers wanting to write if (m_nWaitingWriters > 0) { // Writers are waiting and they take priority over readers m_nActive = -1; // A writer will get access hSem = m_hSemWriters; // Writers wait on this semaphore m_nWaitingWriters--; // One less writer will be waiting // NOTE: The semaphore will release only 1 writer thread ATLTRACE( _T("\b\t\t\t\t%s: Unlock - m_nWaitingWriters: %d\n"), m_sName, m_nWaitingWriters); } else if (m_nWaitingReaders > 0) { // Readers are waiting and no writers are waiting m_nActive = m_nWaitingReaders;// All readers get access m_nWaitingReaders = 0; // No readers will be waiting hSem = m_hSemReaders; // Readers wait on this semaphore lCount = m_nActive; // Semaphore releases all readers ATLTRACE( _T("\b\t\t\t\t%s: Unlock - m_nWaitingReaders: %d\n"), m_sName, m_nWaitingReaders); } else { // There are no threads waiting at all; // no semaphore gets released ATLTRACE( _T("\b\t\t\t\t%s: Unlock - m_nR/W = 0\n"), m_sName); } } } // End of CS Lock scope if (NULL != hSem) { // Some threads are to be released ReleaseSemaphore(hSem, lCount, NULL); } } // Implementation private: void WaitToRead( LPCTSTR szOperation ) { BOOL bResourceWritePending = FALSE; { // Lock scope // Ensure exclusive access to member variables CAutoLockT< CLockableCS > lock( &m_InternalCS ); // Are there writers waiting or is a writer writing? bResourceWritePending = (m_nWaitingWriters || (m_nActive < AT_NONE)); if (bResourceWritePending) { // This reader must wait, // increment the count of waiting readers m_nWaitingReaders++; } else { ATLTRACE( _T("%s: WaitToRead - m_nActive: %d\tOp:%s\n"), m_sName, m_nActive, ((szOperation) ? szOperation : _T(""))); // This reader can read, increment the count of active readers m_nActive++; } } // End of Lock scope if (bResourceWritePending) { ATLTRACE( _T("%s: WaitToRead - m_nWaitingReaders: %d\tOp:%s\n"), m_sName, m_nWaitingReaders, ((szOperation) ? szOperation : _T(""))); // This thread must wait WaitForSingleObject(m_hSemReaders, INFINITE); } } void WaitToWrite( LPCTSTR szOperation ) { BOOL bResourceOwned = FALSE; { // Lock scope // Ensure exclusive access to member variables CAutoLockT< CLockableCS > lock( &m_InternalCS ); // Are there any threads accessing the resource? bResourceOwned = (AT_NONE != m_nActive); if(bResourceOwned) { // This writer must wait; // increment the count of waiting writers m_nWaitingWriters++; } else { ATLTRACE( _T("%s: WaitToWrite %s - Okay TID: %d\n"), m_sName, ((szOperation) ? szOperation : _T("")), GetCurrentThreadId()); // This writer can write, prevent access to others m_nActive = AT_ONEWRITER; } } // End of lock scope if (bResourceOwned) { ATLTRACE( _T("%s: WaitToWrite %s - m_nWaitingWriters: %d TID: %d\n"), m_sName, ((szOperation) ? szOperation : _T("")), m_nWaitingWriters, GetCurrentThreadId()); // This thread must wait WaitForSingleObject(m_hSemWriters, INFINITE); } } }; } // End of namespace AutoLock