Introduction
This article develops a mutex pool manager. The manager is used to manage a mutex pool with a huge mutex number. The mutex pool is allocated from the heap or shared memory based on the manager name parameter specified. An unnamed manager allocates the pool from the heap. Mutexes with an unnamed manager can synchronize threads within a single process. On the other hand, a named manager allocates the pool from the shared memory. Mutexes with a named manager can synchronize threads between processes. The manager can grow the mutex pool automatically based on runtime demand.
The mutex in the pool is implemented using the Windows API InterlockedExchange
; the Windows event object is used to synchronize the threads only if there is contention.
If you have a page based memory buffer, the page number is huge, and each page needs a mutex; or if you have an in-memory hash table, the hash bucket number is huge, and each bucket needs a mutex, and the mutex pool manager may serve you very well.
How to Use the Mutex Pool Manager
The first step is to create a mutex pool manager object. The constructor of the mutex manager class is as follows:
CMutexManager::CMutexManager(DWORD dwInitCount, DWORD dwIncCount, const wchar_t *pszName)
dwInitCount
specifies the initial mutex count created by the manager. dwIncCount
specifies the memory allocation granularity for extending the mutex pool. As the pool grows, memory is allocated in units of dwIncCount
mutexes. pszName
specifies the manager name, it is limited to 128 characters. Name comparison is case sensitive; if the name matches the name of an existing named mutex manager, dwInitCount
and dwIncCount
are ignored because they have already been set by the process that originally created the manager. If pszName
is NULL
, the mutex manager is created without a name. The mutexes with a named manager can synchronize threads between processes, the mutexes with an unnamed manager can synchronize threads within a single process.
The mutex manager exposes three public methods for the applications to call, they are:
bool CMutexManager::CreateMutex(DWORD& dwIndex)
This method creates a mutex from the mutex pool, sets the mutex reference count to one, and returns the index of the mutex being created. A thread must call CloseMutex
for each time that it creates the mutex.
bool CMutexManager::CreateMutex(DWORD& dwIndex)
{
if (m_bInitialized == false)
{
return false;
}
CAutoLock<CSysMutex> lockAuto(m_pSysMutex);
MutexSharedManagerHeader *pSharedHeader =
m_sManagerHeader.pSharedHeader;
if (pSharedHeader->dwMutexNextFree == INVALID_MUTEX_ID)
{
if (pSharedHeader->dwMutexCount == MAX_MUTEX_COUNT)
{
return false;
}
DWORD dwCount = MAX_MUTEX_COUNT - pSharedHeader->dwMutexCount;
dwCount = (dwCount >= pSharedHeader->dwIncCount) ?
pSharedHeader->dwIncCount : dwCount;
if (!CreateMutexBlock(dwCount, m_strName.c_str()))
{
return false;
}
}
dwIndex = pSharedHeader->dwMutexNextFree;
Mutex *pMutex = NULL;
while (!GetMutex(dwIndex, &pMutex))
{
if (!CreateMutexBlock(pSharedHeader->dwIncCount,
m_strName.c_str()))
{
return false;
}
}
pSharedHeader->dwMutexNextFree = pMutex->dwMutexNextFree;
--pSharedHeader->dwMutexFree;
++pSharedHeader->dwMutexInUse;
if (pSharedHeader->dwMutexInUse > pSharedHeader->dwMutexInUseMax)
{
pSharedHeader->dwMutexInUseMax = pSharedHeader->dwMutexInUse;
}
memset(pMutex, 0, sizeof(pMutex));
pMutex->dwIndex = dwIndex;
pMutex->dwMutexRefCount++;
return true;
}
bool CMutexManager::OpenMutex(DWORD dwIndex)
This method opens a mutex that has been created before, and increases the mutex reference count by one. A thread must call CloseMutex
once for each time that it opens the mutex.
bool CMutexManager::OpenMutex(DWORD dwIndex)
{
if (m_bInitialized == false)
{
return false;
}
CAutoLock<CSysMutex> lockAuto(m_pSysMutex);
MutexSharedManagerHeader *pSharedHeader =
m_sManagerHeader.pSharedHeader;
if (dwIndex >= pSharedHeader->dwMutexCount)
{
return false;
}
Mutex *pMutex = NULL;
while (!GetMutex(dwIndex, &pMutex))
{
if (!CreateMutexBlock(pSharedHeader->dwIncCount,
m_strName.c_str()))
{
return false;
}
}
if (pMutex->dwMutexRefCount == 0)
{
return false;
}
pMutex->dwMutexRefCount++;
return true;
}
bool CMutexManager::CloseMutex(DWORD dwIndex)
For a mutex being created or opened before, this method decreases the mutex reference count by one. If the reference count reaches 0, the mutex manager will mark the mutex free and release it back to the mutex pool. A thread must call CloseMutex
once for each time that it creates or opens the mutex.
bool CMutexManager::CloseMutex(DWORD dwIndex)
{
if (m_bInitialized == false)
{
return false;
}
CAutoLock<CSysMutex> lockAuto(m_pSysMutex);
MutexSharedManagerHeader *pSharedHeader =
m_sManagerHeader.pSharedHeader;
if (dwIndex >= pSharedHeader->dwMutexCount)
{
return false;
}
Mutex *pMutex = NULL;
while (!GetMutex(dwIndex, &pMutex))
{
if (!CreateMutexBlock(pSharedHeader->dwIncCount, m_strName.c_str()))
{
return false;
}
}
pMutex->dwMutexRefCount--;
if (pMutex->dwMutexRefCount == 0)
{
pMutex->dwMutexNextFree = pSharedHeader->dwMutexNextFree;
pSharedHeader->dwMutexNextFree = pMutex->dwIndex;
++pSharedHeader->dwMutexFree;
--pSharedHeader->dwMutexInUse;
}
return true;
}
The helper class CMutex
is provided to lock and unlock a mutex. The constructor of the class is as follows:
CMutex::CMutex(CMutexManager *pManager, DWORD dwIndex)
pManager
specifies the mutex pool manager, dwIndex
specifies the mutex index.
The class exposes two public methods for the applications to call, they are Lock()
and Unlock()
.
bool CMutex::Lock()
This method waits for the ownership of the specified mutex object. The method returns when the calling thread is granted ownership. After a thread has ownership of a mutex object, it can make additional calls to Lock()
without blocking its execution. This prevents a thread from deadlocking itself while waiting for a mutex that it already owns. A thread must call Unlock()
once for each time that it calls Lock()
.
The implementation of Lock()
:
bool CMutex::Lock()
{
if ((m_pMutex == NULL) || (m_pManager == NULL))
{
return false;
}
DWORD dwProcessId = GetCurrentProcessId();
DWORD dwThreadId = GetCurrentThreadId();
CSysEvent *pEvent = NULL;
while (true)
{
EnterCriticalSection();
if (m_pMutex->bLocked == false)
{
m_pMutex->bLocked = true;
m_pMutex->dwLockRefCount++;
m_pMutex->dwProcessId = dwProcessId;
m_pMutex->dwThreadId = dwThreadId;
delete pEvent;
LeaveCriticalSection();
return true;
}
if ((m_pMutex->dwProcessId == dwProcessId) &&
(m_pMutex->dwThreadId == dwThreadId))
{
m_pMutex->dwLockRefCount++;
delete pEvent;
LeaveCriticalSection();
return true;
}
m_pMutex->dwThreadsWaiting++;
if (pEvent == NULL)
{
std::wstring strName;
if (!GetEventName(strName))
{
LeaveCriticalSection();
return false;
}
pEvent = new (std::nothrow) CSysEvent(false, false,
strName.c_str(), NULL);
if (pEvent == NULL)
{
LeaveCriticalSection();
return false;
}
}
LeaveCriticalSection();
if (!pEvent->Lock(INFINITE))
{
delete pEvent;
return false;
}
}
delete pEvent;
LeaveCriticalSection();
return true;
}
bool CMutex::Unlock()
This method releases the ownership of the specified mutex object. A thread uses the Lock
method to acquire ownership of a mutex object. To release its ownership, the thread must call Unlock()
once for each time that it calls the Lock()
method.
The implementation of Unlock()
:
bool CMutex::Unlock()
{
if ((m_pMutex == NULL) || (m_pManager == NULL))
{
return false;
}
EnterCriticalSection();
DWORD dwProcessId = GetCurrentProcessId();
DWORD dwThreadId = GetCurrentThreadId();
if ((m_pMutex->dwProcessId != dwProcessId) ||
(m_pMutex->dwThreadId != dwThreadId))
{
LeaveCriticalSection();
return false;
}
if (m_pMutex->bLocked == false)
{
LeaveCriticalSection();
return false;
}
m_pMutex->dwLockRefCount--;
if (m_pMutex->dwLockRefCount > 0)
{
LeaveCriticalSection();
return true;
}
m_pMutex->bLocked = false;
m_pMutex->dwProcessId = 0;
m_pMutex->dwThreadId = 0;
if (m_pMutex->dwThreadsWaiting > 0)
{
std::wstring strName;
if (!GetEventName(strName))
{
LeaveCriticalSection();
return false;
}
CSysEvent *pEvent = new (std::nothrow)CSysEvent(false, false,
strName.c_str(), NULL);
if (pEvent == NULL)
{
LeaveCriticalSection();
return false;
}
if (!pEvent->SetEvent ())
{
LeaveCriticalSection();
delete pEvent;
return false;
}
delete pEvent;
m_pMutex->dwThreadsWaiting--;
}
LeaveCriticalSection();
return true;
}
The spin lock methods used by Lock()
and Unlock()
are:
bool CMutex::EnterCriticalSection()
bool CMutex::EnterCriticalSection()
{
if (m_pMutex == 0)
{
return false;
}
while (InterlockedExchange(&(m_pMutex->lSpinLock), 1) != 0)
{
SwitchToThread();
}
return true;
}
bool CMutex::LeaveCriticalSection()
bool CMutex::LeaveCriticalSection()
{
if (m_pMutex == NULL)
{
return false;
}
InterlockedExchange(&(m_pMutex->lSpinLock), 0);
return true;
}
Bibliography
- Dan Chou. "A Quick and Versatile Synchronization Object" (MSDN library, Technical articles).