Search This Blog

Friday, June 10, 2005

Mutexes - Windows Implementation

Really, there isn't a great deal to say about the Windows version of the mutex, either. It's pretty similar to the fast event and fast semaphore already written. In this case, the atomic variable is a count of the number of would-be threads inside the mutex - at most one will be inside the mutex, and the rest will be waiting on the semaphore. Thus, Enter increments the count, waiting if the count was nonzero; Leave decrements the count, and releases a thread if the count is nonzero after decrementing. Of course, if possible, Enter spins to try to grab the mutex before ultimately incrementing the counter and going into wait. The code:

class CMutex // Windows version of CMutex (fast). This is actually platform-independent, but we need to use the native POSIX mutex type so that we can use condition variables.
{
private:
CSemaphore m_sem; // Semaphore threads waiting on the mutex will wait on

unsigned int m_nSpinCount; // Constant. Number of times to spin before waiting on the event

int32 m_nLockCount; // Interlocked. Number of threads trying to lock the mutex (also the number of threads waiting on the semaphore + 1)

public:
// The spin count is used on multi-processor systems (and will in fact do more harm than good on single-processor systems). It allows a "grace period" where the mutex will spin on the CPU waiting for another processor to release the mutex. This can be much faster than waiting on the semaphore, but just wastes CPU cycles on a single-processor system. Spinning may not be useful if threads typically hold the mutex for very long time periods.
inline CMutex(unsigned int nSpinCount = 0) : m_sem(0)
{
m_nSpinCount = nSpinCount;

m_nLockCount = 0;
}

inline ~CMutex()
{
}

// Tries to enter the mutex, but will fail if the mutex cannot be entered without waiting. Returns true if the mutex was entered.
inline bool TryEnter()
{
// If the lock count was 0, we now own the mutex
return (AtomicCompareExchange(&m_nLockCount, 1, 0) == 0);
}

// Waits indefinitely for the mutex. This function is NOT reentrant. Calling it twice in a row in a thread without calling Leave will cause the thread to permanently deadlock. Always make sure your Enter is paired with a Leave (or use the auto-lock below).
void Enter()
{
// Spin first, in case we can get it without waiting
unsigned int nSpinCount = m_nSpinCount;
while (nSpinCount-- > 0)
{
if (TryEnter())
return;
}

// Try to get the mutex
int32 nLockCount = AtomicIncrement(&m_nLockCount);

if (nLockCount == 1)
return; // No thread held the mutex. We do, now.

// Another thread held the mutex. We have to wait.
m_sem.Wait();
}

inline void Leave()
{
int32 nLockCount = AtomicDecrement(&m_nLockCount);

if (nLockCount > 0)
m_sem.Post(); // Somebody's waiting on the mutex. Release one waiter.
}
};

No comments: