/*********************************************************************
CReaderWriterLock: A simple and fast reader-writer lock class in C++
has characters of .NET ReaderWriterLock class
Copyright (C) 2006 Quynh Nguyen Huu
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
Email questions, comments or suggestions to quynhnguyenhuu@gmail.com
*********************************************************************/
#include "stdafx.h"
#include <crtdbg.h>
#include "ReaderWriterLock.h"
/////////////////////////////////////////////////////////////////
// Following macros make this file can be used in non-MFC project
#ifndef ASSERT
// Define ASSERT macro
# define ASSERT _ASSERT
// Define VERIFY macro
# ifdef _DEBUG
# define VERIFY ASSERT
# define DEBUG_ONLY(f) ((void)(f))
# else
# define VERIFY(f) ((void)(f))
# define DEBUG_ONLY(f)
# endif
#endif
///////////////////////////////////////////////////////
// CReaderWriterLockNonReentrance implementation
CReaderWriterLockNonReentrance::CReaderWriterLockNonReentrance()
{
ZeroMemory(this, sizeof(*this));
#if (_WIN32_WINNT >= 0x0403)
InitializeCriticalSectionAndSpinCount(&m_cs, READER_WRITER_SPIN_COUNT);
#else
InitializeCriticalSection(&m_cs);
#endif
}
CReaderWriterLockNonReentrance::~CReaderWriterLockNonReentrance()
{
_ASSERT( (NULL == m_hSafeToReadEvent) &&
(NULL == m_hSafeToWriteEvent) );
DeleteCriticalSection(&m_cs);
}
bool CReaderWriterLockNonReentrance::_ReaderWait(DWORD dwTimeout) throw()
{
bool blCanRead;
++m_iNumOfReaderWaiting;
if(NULL == m_hSafeToReadEvent)
{
m_hSafeToReadEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
}
if(INFINITE == dwTimeout) // INFINITE is a special value
{
do
{
LeaveCS();
WaitForSingleObject(m_hSafeToReadEvent, INFINITE);
// There might be one or more Writers entered, that's
// why we need DO-WHILE loop here
EnterCS();
}
while(0 != m_iNumOfWriter);
++m_iNumOfReaderEntered;
blCanRead = TRUE;
}
else
{
LeaveCS();
DWORD const dwBeginTime = GetTickCount();
DWORD dwConsumedTime = 0;
for(;;)
{
blCanRead = (WAIT_OBJECT_0 == WaitForSingleObject(m_hSafeToReadEvent,
dwTimeout - dwConsumedTime));
EnterCS();
if(0 == m_iNumOfWriter)
{
// Regardless timeout or not, there is no Writer
// So it's safe to be Reader right now
++m_iNumOfReaderEntered;
blCanRead = TRUE;
break;
}
if(FALSE == blCanRead)
{
// Timeout after waiting
break;
}
// There are some Writers have just entered
// So leave CS and prepare to try again
LeaveCS();
dwConsumedTime = GetTickCount() - dwBeginTime;
if(dwConsumedTime > dwTimeout)
{
// Don't worry why the code here looks stupid
// Because this case rarely happens, it's better
// to optimize code for the usual case
blCanRead = FALSE;
EnterCS();
break;
}
}
}
if(0==--m_iNumOfReaderWaiting)
{
CloseHandle(m_hSafeToReadEvent);
m_hSafeToReadEvent = NULL;
}
return blCanRead;
}
void CReaderWriterLockNonReentrance::_ReaderRelease()
{
INT _iNumOfReaderEntered = --m_iNumOfReaderEntered;
_ASSERT(0 <= _iNumOfReaderEntered);
if( (0 == _iNumOfReaderEntered) &&
(NULL != m_hSafeToWriteEvent) )
{
SetEvent(m_hSafeToWriteEvent);
}
}
bool CReaderWriterLockNonReentrance::_WriterWaitAndLeaveCSIfSuccess(DWORD dwTimeout)
{
//EnterCS();
_ASSERT(0 != dwTimeout);
// Increase Writer-counter & reset Reader-event if necessary
INT _iNumOfWriter = ++m_iNumOfWriter;
if( (1 == _iNumOfWriter) && (NULL != m_hSafeToReadEvent) )
{
ResetEvent(m_hSafeToReadEvent);
}
if(NULL == m_hSafeToWriteEvent)
{
m_hSafeToWriteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
}
LeaveCS();
bool blCanWrite = (WAIT_OBJECT_0 == WaitForSingleObject(m_hSafeToWriteEvent, dwTimeout));
if(FALSE == blCanWrite)
{
// Undo what we changed after timeout
EnterCS();
if(0 == --m_iNumOfWriter)
{
CloseHandle(m_hSafeToWriteEvent);
m_hSafeToWriteEvent = NULL;
if(0 == m_iNumOfReaderEntered)
{
// Although it was timeout, it's still safe to be writer now
++m_iNumOfWriter;
LeaveCS();
blCanWrite = TRUE;
}
else if(m_hSafeToReadEvent)
{
SetEvent(m_hSafeToReadEvent);
}
}
}
return blCanWrite;
}
bool CReaderWriterLockNonReentrance::_UpgradeToWriterLockAndLeaveCS(DWORD dwTimeout) throw()
{
_ASSERT(m_iNumOfReaderEntered > 0);
if(0 == dwTimeout)
{
LeaveCS();
return FALSE;
}
--m_iNumOfReaderEntered;
bool blCanWrite = _WriterWaitAndLeaveCSIfSuccess(dwTimeout);
if(FALSE == blCanWrite)
{
// Now analyze why it was failed to have suitable action
if(0 == m_iNumOfWriter)
{
_ASSERT(0 < m_iNumOfReaderEntered);
// There are some readers still owning the lock
// It's safe to be a reader again after failure
++m_iNumOfReaderEntered;
}
else
{
// Reach to here, it's NOT safe to be a reader immediately
_ReaderWait(INFINITE);
if(1 == m_iNumOfReaderEntered)
{
// After wait, now it's safe to be writer
_ASSERT(0 == m_iNumOfWriter);
m_iNumOfReaderEntered = 0;
m_iNumOfWriter = 1;
blCanWrite = TRUE;
}
}
LeaveCS();
}
return blCanWrite;
}
void CReaderWriterLockNonReentrance::_WriterRelease(bool blDowngrade)
{
_ASSERT(0 == m_iNumOfReaderEntered);
if(blDowngrade)
{
++m_iNumOfReaderEntered;
}
if(0 == --m_iNumOfWriter)
{
if(NULL != m_hSafeToWriteEvent)
{
CloseHandle(m_hSafeToWriteEvent);
m_hSafeToWriteEvent = NULL;
}
if(m_hSafeToReadEvent)
{
SetEvent(m_hSafeToReadEvent);
}
}
else
{
//////////////////////////////////////////////////////////////////////////
// Some WRITERs are queued
_ASSERT( (0 < m_iNumOfWriter) && (NULL != m_hSafeToWriteEvent));
if(FALSE == blDowngrade)
{
SetEvent(m_hSafeToWriteEvent);
}
}
}
bool CReaderWriterLockNonReentrance::AcquireReaderLock(DWORD dwTimeout)
{
bool blCanRead;
EnterCS();
if(0 == m_iNumOfWriter)
{
// Enter successful without wait
++m_iNumOfReaderEntered;
blCanRead = TRUE;
}
else
{
blCanRead = (dwTimeout)? _ReaderWait(dwTimeout) : FALSE;
}
LeaveCS();
return blCanRead;
}
void CReaderWriterLockNonReentrance::ReleaseReaderLock()
{
EnterCS();
_ReaderRelease();
LeaveCS();
}
bool CReaderWriterLockNonReentrance::AcquireWriterLock(DWORD dwTimeout)
{
bool blCanWrite ;
EnterCS();
if(0 == (m_iNumOfWriter | m_iNumOfReaderEntered))
{
++m_iNumOfWriter;
blCanWrite = TRUE;
}
else if(0 == dwTimeout)
{
blCanWrite = FALSE;
}
else
{
blCanWrite = _WriterWaitAndLeaveCSIfSuccess(dwTimeout);
if(blCanWrite)
{
return TRUE;
}
}
LeaveCS();
return blCanWrite;
}
void CReaderWriterLockNonReentrance::ReleaseWriterLock()
{
EnterCS();
_WriterRelease(FALSE);
LeaveCS();
}
void CReaderWriterLockNonReentrance::DowngradeFromWriterLock()
{
EnterCS();
_WriterRelease(TRUE);
LeaveCS();
}
bool CReaderWriterLockNonReentrance::UpgradeToWriterLock(DWORD dwTimeout) throw()
{
EnterCS();
return _UpgradeToWriterLockAndLeaveCS(dwTimeout);
}
// END CReaderWriterLockNonReentrance implementation
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
// CReaderWriterLock implementation
#define READER_RECURRENCE_UNIT 0x00000001
#define READER_RECURRENCE_MASK 0x0000FFFF
#define WRITER_RECURRENCE_UNIT 0x00010000
CReaderWriterLock::CReaderWriterLock()
{
}
CReaderWriterLock::~CReaderWriterLock()
{
}
bool CReaderWriterLock::AcquireReaderLock(DWORD dwTimeout)
{
const DWORD dwCurrentThreadId = GetCurrentThreadId();
m_impl.EnterCS();
CMapThreadToState::iterator ite = m_map.find(dwCurrentThreadId);
if(ite != m_map.end())
{
//////////////////////////////////////////////////////////////////////////
// Current thread was already a WRITER or READER
_ASSERT(0 < ite->second);
ite->second += READER_RECURRENCE_UNIT;
m_impl.LeaveCS();
return TRUE;
}
if(0 == m_impl.m_iNumOfWriter)
{
// There is NO WRITER on this RW object
// Current thread is going to be a READER
++m_impl.m_iNumOfReaderEntered;
m_map.insert(std::make_pair(dwCurrentThreadId, READER_RECURRENCE_UNIT));
m_impl.LeaveCS();
return TRUE;
}
if(0 == dwTimeout)
{
m_impl.LeaveCS();
return FALSE;
}
bool blCanRead = m_impl._ReaderWait(dwTimeout);
if(blCanRead)
{
m_map.insert(std::make_pair(dwCurrentThreadId, READER_RECURRENCE_UNIT));
}
m_impl.LeaveCS();
return blCanRead;
}
void CReaderWriterLock::ReleaseReaderLock()
{
const DWORD dwCurrentThreadId = GetCurrentThreadId();
m_impl.EnterCS();
CMapThreadToState::iterator ite = m_map.find(dwCurrentThreadId);
_ASSERT( (ite != m_map.end()) && (READER_RECURRENCE_MASK & ite->second));
const DWORD dwThreadState = (ite->second -= READER_RECURRENCE_UNIT);
if(0 == dwThreadState)
{
m_map.erase(ite);
m_impl._ReaderRelease();
}
m_impl.LeaveCS();
}
bool CReaderWriterLock::AcquireWriterLock(DWORD dwTimeout)
{
const DWORD dwCurrentThreadId = GetCurrentThreadId();
bool blCanWrite;
m_impl.EnterCS();
CMapThreadToState::iterator ite = m_map.find(dwCurrentThreadId);
if(ite != m_map.end())
{
_ASSERT(0 < ite->second);
if(ite->second >= WRITER_RECURRENCE_UNIT)
{
// Current thread was already a WRITER
ite->second += WRITER_RECURRENCE_UNIT;
m_impl.LeaveCS();
return TRUE;
}
// Current thread was already a READER
_ASSERT(1 <= m_impl.m_iNumOfReaderEntered);
if(1 == m_impl.m_iNumOfReaderEntered)
{
// This object is owned by ONLY current thread for READ
// There might be some threads queued to be WRITERs
// but for effectiveness (higher throughput), we allow current
// thread upgrading to be WRITER right now
m_impl.m_iNumOfReaderEntered = 0;
++m_impl.m_iNumOfWriter;
ite->second += WRITER_RECURRENCE_UNIT;
m_impl.LeaveCS();
return TRUE;
}
// Try upgrading from reader to writer
blCanWrite = m_impl._UpgradeToWriterLockAndLeaveCS(dwTimeout);
if(blCanWrite)
{
m_impl.EnterCS();
ite = m_map.find(dwCurrentThreadId);
ite->second += WRITER_RECURRENCE_UNIT;
m_impl.LeaveCS();
}
}
else
{
if(0 == (m_impl.m_iNumOfWriter | m_impl.m_iNumOfReaderEntered))
{
// This RW object is not owned by any thread
// --> it's safe to make this thread to be WRITER
++m_impl.m_iNumOfWriter;
m_map.insert(std::make_pair(dwCurrentThreadId, WRITER_RECURRENCE_UNIT));
m_impl.LeaveCS();
return TRUE;
}
if(0 == dwTimeout)
{
m_impl.LeaveCS();
return FALSE;
}
blCanWrite = m_impl._WriterWaitAndLeaveCSIfSuccess(dwTimeout);
if(blCanWrite)
{
m_impl.EnterCS();
m_map.insert(std::make_pair(dwCurrentThreadId, WRITER_RECURRENCE_UNIT));
}
m_impl.LeaveCS();
}
return blCanWrite;
}
void CReaderWriterLock::ReleaseWriterLock()
{
const DWORD dwCurrentThreadId = GetCurrentThreadId();
m_impl.EnterCS();
CMapThreadToState::iterator ite = m_map.find(dwCurrentThreadId);
_ASSERT( (ite != m_map.end()) && (WRITER_RECURRENCE_UNIT <= ite->second));
const DWORD dwThreadState = (ite->second -= WRITER_RECURRENCE_UNIT);
if(0 == dwThreadState)
{
m_map.erase(ite);
m_impl._WriterRelease(FALSE);
}
else if (WRITER_RECURRENCE_UNIT > dwThreadState)
{
// Down-grading from writer to reader
m_impl._WriterRelease(TRUE);
}
m_impl.LeaveCS();
}
void CReaderWriterLock::ReleaseAllLocks()
{
const DWORD dwCurrentThreadId = GetCurrentThreadId();
m_impl.EnterCS();
CMapThreadToState::iterator ite = m_map.find(dwCurrentThreadId);
if(ite != m_map.end())
{
const DWORD dwThreadState = ite->second;
m_map.erase(ite);
if(WRITER_RECURRENCE_UNIT <= dwThreadState)
{
m_impl._WriterRelease(FALSE);
}
else
{
_ASSERT(0 < dwThreadState);
m_impl._ReaderRelease();
}
}
m_impl.LeaveCS();
}
DWORD CReaderWriterLock::GetCurrentThreadStatus() const throw()
{
DWORD dwThreadState;
const DWORD dwCurrentThreadId = GetCurrentThreadId();
m_impl.EnterCS();
CMapThreadToState::const_iterator ite = m_map.find(dwCurrentThreadId);
if(ite != m_map.end())
{
dwThreadState = ite->second;
m_impl.LeaveCS();
_ASSERT(dwThreadState > 0);
}
else
{
dwThreadState = 0;
m_impl.LeaveCS();
}
return dwThreadState;
}
void CReaderWriterLock::GetCurrentThreadStatus(DWORD* lpdwReaderLockCounter,
DWORD* lpdwWriterLockCounter) const
{
const DWORD dwThreadState = GetCurrentThreadStatus();
if(NULL != lpdwReaderLockCounter)
{
*lpdwReaderLockCounter = (dwThreadState & READER_RECURRENCE_MASK);
}
if(NULL != lpdwWriterLockCounter)
{
*lpdwWriterLockCounter = (dwThreadState / WRITER_RECURRENCE_UNIT);
}
}