//***************************************************************************** // locks.h // // This class provides a number of locking primitives for multi-threaded // programming. The main class of interest are: // CCritLock Critical section based lock wrapper class. // CExclLock A Spin lock class for classic test & set behavior. // CSingleLock A spin lock with no nesting capabilities. // CAutoLock A helper class to lock/unlock in ctor/dtor. // // CMReadSWrite A highly efficient lock for multiple readers and // single writer behavior. // CAutoReadLock A helper for read locking in ctor/dtor. // CAutoWriteLock A helper for write locking in ctor/dtor. // // Copyright (c) 1996, Microsoft Corp. All rights reserved. //***************************************************************************** #ifndef __LOCKS_H__ #define __LOCKS_H__ //***************************************************************************** // This lock implements a spin lock that does not support nesting. It is very // lean because of this, but locks cannot be nested. //***************************************************************************** class CSingleLock { long volatile m_iLock; // Test and set spin value. public: inline CSingleLock() : m_iLock(0) { } inline ~CSingleLock() { m_iLock = 0; } //***************************************************************************** // This version spins forever until it wins. Nested calls to Lock from the // same thread are not supported. //***************************************************************************** inline void Lock() { // Spin until we win. while (InterlockedExchange((long*)&m_iLock, 1L) == 1L) ; } //***************************************************************************** // This version spins until it wins or times out. Nested calls to Lock from // the same thread are supported. //***************************************************************************** HRESULT Lock( // S_OK, or E_FAIL. DWORD dwTimeout) // Millisecond timeout value, 0 is forever. { DWORD dwTime = 0; // Keep spinning until we get the lock. while (InterlockedExchange((long*)&m_iLock, 1L) == 1L) { // Wait for 1/10 a second. Sleep(100); // See if we have gone over the timeout value. if (dwTimeout) { if ((dwTime += 100) >= dwTimeout) return (E_FAIL); } } return (S_OK); } //***************************************************************************** // Assigning to 0 is thread safe and yields much faster performance than // an Interlocked* operation. //***************************************************************************** inline void Unlock() { m_iLock = 0; } }; //***************************************************************************** // This lock class is based on NT's critical sections and has all of their // semantics. //***************************************************************************** class CCritLock { private: CRITICAL_SECTION m_sCrit; // The critical section to block on. #ifdef _DEBUG BOOL m_bInit; // Track init status. int m_iLocks; // Count of locks. #endif public: inline CCritLock() { #ifdef _DEBUG m_bInit = TRUE; m_iLocks = 0; #endif InitializeCriticalSection(&m_sCrit); } inline ~CCritLock() { _ASSERTE(m_bInit); _ASSERTE(m_iLocks == 0); DeleteCriticalSection(&m_sCrit); } inline void Lock() { _ASSERTE(m_bInit); EnterCriticalSection(&m_sCrit); _ASSERTE(++m_iLocks > 0); } inline void Unlock() { _ASSERTE(m_bInit); _ASSERTE(--m_iLocks >= 0); LeaveCriticalSection(&m_sCrit); } #ifdef _DEBUG inline int GetLockCnt() { return (m_iLocks); } inline BOOL IsLocked() { return (m_iLocks != 0); } #endif }; //***************************************************************************** // Provides a mututal exclusion lock for a resource through a spin lock. This // type of lock does not keep a queue, so thread starvation is theoretically // possible. In addition, thread priority could cause a potential dead lock if // a low priority thread got the lock but didn't get enough time to eventually // free it. // NOTE: There is a bug in the Pentium cache that InterlockedExchange will // force a cache flush of the value. For this reason, doing an assignment // to free the lock is much, much faster than using an Interlocked instruction. //***************************************************************************** class CExclLock { long volatile m_iLock; // Test and set spin value. long m_iNest; // Nesting count. DWORD m_iThreadId; // The thread that owns the lock. public: inline CExclLock() : m_iLock(0), m_iNest(0), m_iThreadId(0) { } inline ~CExclLock() { m_iNest = 0; m_iThreadId = 0; m_iLock = 0; } //***************************************************************************** // This version spins forever until it wins. Nested calls to Lock from the // same thread are supported. //***************************************************************************** inline void Lock() { DWORD iThread; // Local thread ID. // Allow nested calls to lock in the same thread. if ((iThread = GetCurrentThreadId()) == m_iThreadId && m_iLock) { ++m_iNest; return; } // Spin until we win. while (InterlockedExchange((long*)&m_iLock, 1L) == 1L) ; // Store our thread ID and nesting count now that we've won. m_iThreadId = iThread; m_iNest = 1; } //***************************************************************************** // This version spins until it wins or times out. Nested calls to Lock from // the same thread are supported. //***************************************************************************** HRESULT Lock( // S_OK, or E_FAIL. DWORD dwTimeout) // Millisecond timeout value, 0 is forever. { DWORD dwTime = 0; DWORD iThread; // Local thread ID. // Allow nested calls to lock in the same thread. if (m_iLock && (iThread = GetCurrentThreadId()) == m_iThreadId) { ++m_iNest; return (S_OK); } // Keep spinning until we get the lock. while (InterlockedExchange((long*)&m_iLock, 1L) == 1L) { // Wait for 1/10 a second. Sleep(100); // See if we have gone over the timeout value. if (dwTimeout) { if ((dwTime += 100) >= dwTimeout) return (E_FAIL); } } // Store our thread ID and nesting count now that we've won. m_iThreadId = iThread; m_iNest = 1; return (S_OK); } //***************************************************************************** // Assigning to 0 is thread safe and yields much faster performance than // an Interlocked* operation. //***************************************************************************** inline void Unlock() { _ASSERTE(m_iThreadId == GetCurrentThreadId() && m_iNest > 0); // Unlock outer nesting level. if (--m_iNest == 0) { m_iThreadId = 0; m_iLock = 0; } } #ifdef _DEBUG inline BOOL IsLocked() { return (m_iLock); } #endif }; //***************************************************************************** // This helper class automatically locks the given lock object in the ctor and // frees it in the dtor. This makes your code slightly cleaner by not // requiring an unlock in all failure conditions. //***************************************************************************** class CAutoLock { CExclLock *m_psLock; // The lock object to free up. CCritLock *m_psCrit; // Crit lock. CSingleLock *m_psSingle; // Single non-nested lock. int m_iNest; // Nesting count for the item. public: //***************************************************************************** // Use this ctor with the assignment operators to do deffered locking. //***************************************************************************** CAutoLock() : m_psLock(NULL), m_psCrit(NULL), m_psSingle(NULL), m_iNest(0) { } //***************************************************************************** // This version handles a spin lock. //***************************************************************************** CAutoLock(CExclLock *psLock) : m_psLock(psLock), m_psCrit(NULL), m_psSingle(NULL), m_iNest(1) { _ASSERTE(psLock != NULL); psLock->Lock(); } //***************************************************************************** // This version handles a critical section lock. //***************************************************************************** CAutoLock(CCritLock *psLock) : m_psLock(NULL), m_psCrit(psLock), m_psSingle(NULL), m_iNest(1) { _ASSERTE(psLock != NULL); psLock->Lock(); } //***************************************************************************** // This version handles a critical section lock. //***************************************************************************** CAutoLock(CSingleLock *psLock) : m_psLock(NULL), m_psCrit(NULL), m_psSingle(psLock), m_iNest(1) { _ASSERTE(psLock != NULL); psLock->Lock(); } //***************************************************************************** // Free the lock we actually have. //***************************************************************************** ~CAutoLock() { // If we actually took a lock, unlock it. if (m_iNest != 0) { if (m_psLock) { while (m_iNest--) m_psLock->Unlock(); } else if (m_psCrit) { while (m_iNest--) m_psCrit->Unlock(); } else if (m_psSingle) { while (m_iNest--) m_psSingle->Unlock(); } } } //***************************************************************************** // Lock after ctor runs with NULL. //***************************************************************************** void Lock( CSingleLock *psLock) { m_psSingle = psLock; psLock->Lock(); m_iNest = 1; } //***************************************************************************** // Assignment causes a lock to occur. dtor will free the lock. Nested // assignments are allowed. //***************************************************************************** CAutoLock & operator=( // Reference to this class. CExclLock *psLock) // The lock. { _ASSERTE(m_psCrit == NULL && m_psSingle == NULL); ++m_iNest; m_psLock = psLock; psLock->Lock(); return (*this); } //***************************************************************************** // Assignment causes a lock to occur. dtor will free the lock. Nested // assignments are allowed. //***************************************************************************** CAutoLock & operator=( // Reference to this class. CCritLock *psLock) // The lock. { _ASSERTE(m_psSingle == NULL && m_psLock == NULL); ++m_iNest; m_psCrit = psLock; psLock->Lock(); return (*this); } //***************************************************************************** // Assignment causes a lock to occur. dtor will free the lock. Nested // assignments are allowed. //***************************************************************************** CAutoLock & operator=( // Reference to this class. CSingleLock *psLock) // The lock. { _ASSERTE(m_psCrit == NULL && m_psLock == NULL); ++m_iNest; m_psSingle = psLock; psLock->Lock(); return (*this); } }; #endif // __LOCKS_H__