// Instances of this class will be accessed by multiple threads. So,
// all members of this class (except the constructor and destructor)
// must be thread-safe.
class CResGuard {
public:
CResGuard() { m_lGrdCnt = 0; InitializeCriticalSection(&m_cs); }
~CResGuard() { DeleteCriticalSection(&m_cs); }
// IsGuarded is used for debugging
BOOL IsGuarded() const { return(m_lGrdCnt > 0); }
public:
class CGuard {
public:
CGuard(CResGuard& rg) : m_rg(rg) { m_rg.Guard(); };
~CGuard() { m_rg.Unguard(); }
private:
CResGuard& m_rg;
};
private:
void Guard() { EnterCriticalSection(&m_cs); m_lGrdCnt++; }
void Unguard() { m_lGrdCnt--; LeaveCriticalSection(&m_cs); }
// Guard/Unguard can only be accessed by the nested CGuard class.
friend class CResGuard::CGuard;
private:
CRITICAL_SECTION m_cs;
long m_lGrdCnt; // # of EnterCriticalSection calls
};
Figure 2 Using the CGuard Class
struct SomeDataStruct {
•••
} g_SomeSharedData;
// Create a CResGuard that protects g_SomeSharedData
// Note: The constructor initializes the critical section and
// the destructor deletes the critical section.
CResGuard g_rgSomeSharedData;
void AFunction () {
// This function touches the shared data
// Protect the resource from being accessed from multiple threads.
CResGuard::CGuard gDummy(g_rgSomeSharedData);
// Enters the critical section
// Touch the g_SomeSharedData resource
•••
} // Note: LeaveCriticalSection is called when gDummy goes out of scope
Figure 3 CinterlockedType
// Instances of this class will be accessed by multiple threads. So,
// all members of this class (except the constructor and destructor)
// must be thread-safe.
template <class TYPE>
class CInterlockedType {
public: // Public member functions
// Note: Constructors & destructors are always thread-safe
CInterlockedType() { }
CInterlockedType(const TYPE& TVal) { m_TVal = TVal; }
virtual ~CInterlockedType() { }
// Cast operator to make writing code that uses
// thread-safe data type easier
operator TYPE() const {
#ifdef MUTABLE_SUPPORTED
CResGuard::CGuard x(m_rg);
#else
CResGuard::CGuard x(const_cast<CResGuard&>(m_rg));
#endif
return(GetVal());
}
protected: // Protected function to be called by derived class
TYPE& GetVal() {
assert(m_rg.IsGuarded());
return(m_TVal);
}
const TYPE& GetVal() const {
assert(m_rg.IsGuarded());
return(m_TVal);
}
TYPE SetVal(const TYPE& TNewVal) {
assert(m_rg.IsGuarded());
TYPE& TVal = GetVal();
if (TVal != TNewVal) {
TYPE TPrevVal = TVal;
TVal = TNewVal;
OnValChanged(TNewVal, TPrevVal);
}
return(TVal);
}
protected: // Overridable functions
virtual void OnValChanged(
const TYPE& TNewVal, const TYPE& TPrevVal) const {
// Nothing to do here
}
protected:
// Protected guard for use by derived class functions
#ifdef MUTABLE_SUPPORTED
mutable CResGuard m_rg;
#else
CResGuard m_rg;
#endif
private: // Private data members
TYPE m_TVal;
};
Figure 4 CinterlockedScalar
// Instances of this class will be accessed by multiple threads. So,
// all members of this class (except the constructor and destructor)
// must be thread-safe.
template <class TYPE>
class CInterlockedScalar :
protected CInterlockedType<TYPE> {
public:
CInterlockedScalar(TYPE TVal = 0)
: CInterlockedType<TYPE>(TVal) {
}
~CInterlockedScalar() { /* Nothing to do */ }
// C++ does not allow operator cast to be inherited.
operator TYPE() const {
return(CInterlockedType<TYPE>::operator TYPE());
}
TYPE operator=(TYPE TVal) {
CResGuard::CGuard x(m_rg);
return(SetVal(TVal));
}
TYPE operator++(int) { // Postfix increment operator
CResGuard::CGuard x(m_rg);
TYPE TPrevVal = GetVal();
SetVal(TPrevVal + 1);
return(TPrevVal); // Return value BEFORE increment
}
TYPE operator--(int) { // Postfix decrement operator
CResGuard::CGuard x(m_rg);
TYPE TPrevVal = GetVal();
SetVal(TPrevVal - 1);
return(TPrevVal); // Return value BEFORE decrement
}
TYPE operator += (TYPE op)
{ CResGuard::CGuard x(m_rg); return(SetVal(GetVal() + op)); }
TYPE operator++()
{ CResGuard::CGuard x(m_rg); return(SetVal(GetVal() + 1)); }
TYPE operator -= (TYPE op)
{ CResGuard::CGuard x(m_rg); return(SetVal(GetVal() - op)); }
TYPE operator--()
{ CResGuard::CGuard x(m_rg); return(SetVal(GetVal() - 1)); }
TYPE operator *= (TYPE op)
{ CResGuard::CGuard x(m_rg); return(SetVal(GetVal() * op)); }
TYPE operator /= (TYPE op)
{ CResGuard::CGuard x(m_rg); return(SetVal(GetVal() / op)); }
TYPE operator %= (TYPE op)
{ CResGuard::CGuard x(m_rg); return(SetVal(GetVal() % op)); }
TYPE operator ^= (TYPE op)
{ CResGuard::CGuard x(m_rg); return(SetVal(GetVal() ^ op)); }
TYPE operator &= (TYPE op)
{ CResGuard::CGuard x(m_rg); return(SetVal(GetVal() & op)); }
TYPE operator |= (TYPE op)
{ CResGuard::CGuard x(m_rg); return(SetVal(GetVal() | op)); }
TYPE operator <<=(TYPE op)
{ CResGuard::CGuard x(m_rg); return(SetVal(GetVal() << op)); }
TYPE operator >>=(TYPE op)
{ CResGuard::CGuard x(m_rg); return(SetVal(GetVal() >> op)); }
};
Figure 5 CwhenZero
// Instances of this class will be accessed by multiple threads. So,
// all members of this class (except the constructor and destructor)
// must be thread-safe.
template <class TYPE>
class CWhenZero : public CInterlockedScalar<TYPE> {
public:
CWhenZero(TYPE TVal = 0, BOOL fManualReset = TRUE)
: CInterlockedScalar<TYPE>(TVal) {
// The event should be signaled if TVal is 0
m_hevtZero = CreateEvent(NULL, fManualReset, (TVal == 0), NULL);
// The event should be signaled if TVal is NOT 0
m_hevtNotZero = CreateEvent(NULL, fManualReset, (TVal != 0), NULL);
}
~CWhenZero() {
CloseHandle(m_hevtZero);
CloseHandle(m_hevtNotZero);
}
// C++ does not allow operator= to be inherited.
TYPE operator=(TYPE x) {
return(CInterlockedScalar<TYPE>::operator=(x));
}
// Return handle to event signaled when value is zero
operator HANDLE() const { return(m_hevtZero); }
// Return handle to event signaled when value is not zero
HANDLE GetNotZeroHandle() const { return(m_hevtNotZero); }
// C++ does not allow operator cast to be inherited.
operator TYPE() const {
return(CInterlockedScalar<TYPE>::operator TYPE());
}
protected:
void OnValChanged(const TYPE& TNewVal, const TYPE& TPrevVal) const {
// For best performance, avoid jumping to
// kernel mode if we don't have to
if ((TNewVal == 0) && (TPrevVal != 0)) {
SetEvent(m_hevtZero);
ResetEvent(m_hevtNotZero);
}
if ((TNewVal != 0) && (TPrevVal == 0)) {
ResetEvent(m_hevtZero);
SetEvent(m_hevtNotZero);
}
}
private:
HANDLE m_hevtZero; // Signaled when data value is 0
HANDLE m_hevtNotZero; // Signaled when data value is not 0
};
#endif // __INTERLOCKED_H__
Figure 6 IntLockTest.h
/*************************************************************
Module name: IntLockTest.h
Notices: Written 1997 by Jeffrey Richter
Description: Tests CWhenZero class.
*************************************************************/
#define STRICT
#include <Windows.h>
#include <assert.h>
#include "Interlocked.h"
//////////////////////////////////////////////////////////////
// Set to TRUE when worker threads should terminate cleanly.
BOOL g_fQuit = FALSE;
//////////////////////////////////////////////////////////////
DWORD WINAPI WorkerThread (LPVOID p) {
CWhenZero<BYTE>& bVal = * (CWhenZero<BYTE> *) p;
// Should worker thread terminate?
while (!g_fQuit) {
// Wait for something to do
WaitForSingleObject(bVal.GetNotZeroHandle(), INFINITE);
// If we should quit, quit
if (g_fQuit) continue;
// Do something
MessageBox(NULL, __TEXT("We have something to do"),
__TEXT("Worker thread"), MB_OK);
bVal--; // We're done
// Wait for all worker threads to stop
WaitForSingleObject(bVal, INFINITE);
}
MessageBox(NULL, __TEXT("Worker is terminating"),
__TEXT("Worker thread"), MB_OK);
return(0);
}
//////////////////////////////////////////////////////////////
int WINAPI WinMain (HINSTANCE hinst,
HINSTANCE hinstPrev, LPSTR lp, int n) {
// Initialize to indicate that NO worker threads have anything to do
CWhenZero<BYTE> bVal = 0;
// Create the worker threads
const int nMaxThreads = 2;
HANDLE hThreads[nMaxThreads];
for (int nThread = 0; nThread < nMaxThreads; nThread++) {
DWORD dwThreadId;
hThreads[nThread] = CreateThread(NULL, 0,
WorkerThread, (PVOID) &bVal, 0, &dwThreadId);
}
do {
// Find out if there is more work to be done
// or if the process should terminate
n = MessageBox(NULL,
__TEXT("Yes: Give worker threads something to do\nNo: Quit"),
__TEXT("Primary thread"), MB_YESNO);
// Set the flag so that the worker threads will see
// that there is no more work to do.
if (n == IDNO) g_fQuit = TRUE;
bVal = nMaxThreads; // Wake the worker threads
if (n == IDYES) {
// There is work to do, wait for the worker threads to finish
WaitForSingleObject(bVal, INFINITE);
}
} while (n == IDYES);
// There is no more work to do, the process wants to die.
// Wait for the worker threads to terminate
WaitForMultipleObjects(nMaxThreads, hThreads, TRUE, INFINITE);
// Close the worker thread handles.
for (nThread = 0; nThread < nMaxThreads; nThread++)
CloseHandle(hThreads[nThread]);
// Tell the user that the process is dying
MessageBox(NULL,
__TEXT("Primary thread is terminating"),
__TEXT("Primary thread"), MB_OK);
return(0);
}
//////////////////////// End Of File /////////////////////////