读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。
现在win32的api,用c++实现自己的读写锁。这组api包括:createmutex,createevent,waitforsingleobject,waitformultipleobjects,resetevent,releasemutex,setevent,closehandle.
Windows在Vista 和 Server2008以后才开始提供读写锁API,即SRW系列函数(InitializeSRWLock, AcquireSRWLockShared,
AcquireSRWLockExclusive等).那么如何实现一个自己的读写锁呢?
读写锁的目的和要求
读写锁的最基本目的是读锁可以共享,写锁必须独占.另外,还有两点需要特别考虑:
1) 如果有写锁请求在等待,则新的读锁请求也应该等待.否则程序可能永远也没有机会获得写锁.
2) 一旦写锁被释放,所有在等待中的读锁和写锁请求应该可以公平竞争,而不管请求的先后,只要之前已经在等待就应该有获得锁的机会.
如果说第一点还可以再斟酌一下的话,第二点应该是必须要保证的.
读写锁的实现
总体思路比较简单:读写锁内部维护一些状态值,用临界端CRITICAL_SECTION保护这些状态值访问的原子性和事件对象配合实现等待.
进入锁的情况:
1) 如果当前锁状态为空闲,则不管写锁还是读锁请求都允许进入,并设置相应的状态值.
2) 如果当前锁状态是读,新的写锁请求需要等待事件通知,并把写锁等待计数加一.
3) 如果当前锁状态是读,新的读锁请求:
(1) 如果没有写锁请求在等待,则允许读锁进入,并把读锁计数加一.
(2) 如果有写锁请求正在等待,则等待事件通知,并读锁等待计数加一(这样做的目的如上文所述,要使写锁有机会进入).
4) 如果当前锁状态为写,则不管读锁请求还是写锁请求,都等待事件通知并分别给读锁等待计数或者写锁等待计数加一.
解锁的情况:
如果锁释放时,读锁等待计数或者写锁等待计数不为0,则触发事件对象.
我使用手动事件对象,这样的话一旦锁被释放,所有正在等待的锁请求都将被激活,然后重新以竞争临界段的方式竞争锁进入权以保证公平性.不管等待请求时的先后,只要是锁释放前进入等待状态则锁一旦释放获得进入权的机会是均等的.
读写锁实现参考代码1:
#define RWLOCK_IDLE 0 /* 空闲 */
#define RWLOCK_R 0x01 /* 读锁 */
#define RWLOCK_W 0x02 /* 写锁 */
class RWLock
{
private:
int _st; /* 锁状态值 */
int _rlockCount; /* 读锁计数 */
int _rwaitingCount; /* 读等待计数 */
int _wwaitingCount; /* 写等待计数 */
HANDLE _ev; /* 通知事件 Event */
//HANDLE _stLock; /* 访问状态值互斥量 */ /* 如果需要等待超时,则用 Mutex */
CRITICAL_SECTION _stLock;
public:
RWLock(void);
~RWLock(void);
void rlock();
void wlock();
void unlock();
};
RWLock::RWLock(void)
: _rlockCount(0),
_st(RWLOCK_IDLE),
_rwaitingCount(0),
_wwaitingCount(0)
{
//_stLock = CreateMutex(NULL, FALSE, NULL);
//assert(_stLock != INVALID_HANDLE_VALUE);
InitializeCriticalSection(&_stLock);
/*
* 假设当前有多个读锁请求正在等待写锁释放,那么当写锁被释放时,所有这些读锁都应该有机会获得执行.
*/
_ev = CreateEvent(NULL, TRUE, FALSE, NULL);
assert(_ev != INVALID_HANDLE_VALUE);
}
RWLock::~RWLock(void)
{
//CloseHandle(_stLock);
DeleteCriticalSection(&_stLock);
CloseHandle(_ev);
}
void RWLock::rlock()
{
bool isWaitReturn = false;
while(1)
{
//WaitForSingleObject(_stLock, INFINITE);
EnterCriticalSection(&_stLock);
if(isWaitReturn)
{
/*
* 等待事件返回,重新竞争锁.
*/
--_rwaitingCount;
}
if(_st == RWLOCK_IDLE)
{
/*
* 空闲状态,直接得到控制权
*/
_st = RWLOCK_R;
_rlockCount++;
//ReleaseMutex(_stLock);
LeaveCriticalSection(&_stLock);
break;
}
else if( _st == RWLOCK_R)
{
if(_wwaitingCount > 0)
{
/*
* 有写锁正在等待,则一起等待,以使写锁能获得竞争机会.
*/
++_rwaitingCount;
ResetEvent(_ev);
//SignalObjectAndWait(_stLock, _ev, INFINITE, FALSE);
LeaveCriticalSection(&_stLock);
/*
* 虽然 LeaveCriticalSection() 和 WaitForSingleObject() 之间有一个时间窗口,
* 但是由于windows平台的事件信号是不会丢失的,所以没有问题.
*/
WaitForSingleObject(_ev, INFINITE);
/*
* 等待返回,继续尝试加锁.
*/
isWaitReturn = true;
}
else
{
/*
* 得到读锁,计数+1
*/
++_rlockCount;
//ReleaseMutex(_stLock);
LeaveCriticalSection(&_stLock);
break;
}
}
else if(_st == RWLOCK_W)
{
/*
* 等待写锁释放
*/
++_rwaitingCount;
ResetEvent(_ev);
//SignalObjectAndWait(_stLock, _ev, INFINITE, FALSE);
LeaveCriticalSection(&_stLock);
WaitForSingleObject(_ev, INFINITE);
/*
* 等待返回,继续尝试加锁.
*/
isWaitReturn = true;
}
else
{
assert(0);
break;
}
}
}
void RWLock::wlock()
{
bool isWaitReturn = false;
while(1)
{
//WaitForSingleObject(_stLock, INFINITE);
EnterCriticalSection(&_stLock);
if(isWaitReturn) --_wwaitingCount;
if(_st == RWLOCK_IDLE)
{
_st = RWLOCK_W;
//ReleaseMutex(_stLock);
LeaveCriticalSection(&_stLock);
break;
}
else
{
++_wwaitingCount;
ResetEvent(_ev);
//SignalObjectAndWait(_stLock, _ev, INFINITE, FALSE);
LeaveCriticalSection(&_stLock);
WaitForSingleObject(_ev, INFINITE);
isWaitReturn = true;
}
}
}
void RWLock::unlock()
{
//WaitForSingleObject(_stLock, INFINITE);
EnterCriticalSection(&_stLock);
if(_rlockCount > 0)
{
/* 读锁解锁 */
--_rlockCount;
if( 0 == _rlockCount)
{
_st = RWLOCK_IDLE;
/* 释放 */
if( _wwaitingCount > 0 || _rwaitingCount > 0 )
{
/*
* 此时有锁请求正在等待,激活所有等待的线程.(手动事件).
* 使这些请求重新竞争锁.
*/
SetEvent(_ev);
}
else
{
/* 空闲 */
}
}
else
{
/* 还有读锁 */
}
}
else
{
_st = RWLOCK_IDLE;
/* 写锁解锁 */
if( _wwaitingCount > 0 || _rwaitingCount > 0 )
{
/*
* 如果在占有互斥量_stLock的情况下,触发事件,那么可能会使一些锁请求不能得到竞争机会.
* 假设调用unlock时,另一个线程正好调用rlock或者wlock.如果不释放互斥量,只有之前已经等待的锁请求有机会获得锁控制权.
*/
SetEvent(_ev);
}
else
{
/* 空闲 */
}
}
//ReleaseMutex(_stLock);
LeaveCriticalSection(&_stLock);
}
读写锁参考代码2:
class ReadWriteLock
{
public:
int m_currentLevel;
int m_readCount;
HANDLE m_unlockEvent;
HANDLE m_accessMutex;
CRITICAL_SECTION m_csStateChange;
public:
ReadWriteLock
{
m_currentLevel = LOCK_LEVEL_NONE;
m_readCount = 0;
m_unlockEvent = ::CreateEvent( NULL, TRUE, FALSE, NULL );
m_accessMutex = ::CreateMutex( NULL, FALSE, NULL );
::InitializeCriticalSection( &m_csStateChange );
}
~ReadWriteLock
{
::DeleteCriticalSection( &m_csStateChange );
if(m_accessMutex) ::CloseHandle( m_accessMutex );
if(m_unlockEvent) ::CloseHandle( m_unlockEvent );
}
bool lock(level, timeout = INFINITE)
{
bool bresult = true;
DWORD waitResult = 0;
waitResult = ::WaitForSingleObject( m_accessMutex, timeout );
if(waitResult != WAIT_OBJECT_0) return false;
if(level==LOCK_LEVEL_READ && m_currentLevel != LOCK_LEVEL_WRITE )
{
::EnterCriticalSection( &m_csStateChange );
m_currentLevel = level;
m_readCount +=1;
::ReEvent( m_unlockEvent );
::LeaveCriticalSection( &m_csStateChange );
}
else if(level== LOCK_LEVEL_READ && m_currentLevel == LOCK_LEVEL_WRITE )
{
waitResult = ::WaitForSingleObject( m_unlockEvent, timeout );
if(waitResult== WAIT_OBJECT_0)
{
::EnterCriticalSection( &m_csStateChange );
m_currentLevel = level;
m_readCount +=1;
::ReEvent( m_unlockEvent );
::LeaveCriticalSection( &m_csStateChange );
}
else bresult = false;
}
else if(level== LOCK_LEVEL_WRITE && m_currentLevel == LOCK_LEVEL_NONE )
{
::EnterCriticalSection( &m_csStateChange );
m_currentLevel = level;
::ReEvent( m_unlockEvent );
::LeaveCriticalSection( &m_csStateChange );
}
else if(level== LOCK_LEVEL_WRITE && m_currentLevel != LOCK_LEVEL_NONE )
{
waitResult = ::WaitForSingleObject( m_unlockEvent, timeout );[Page]
if(waitResult==WAIT_OBJECT_0 )
{
::EnterCriticalSection( &m_csStateChange );
m_currentLevel = level;
::ReEvent( m_unlockEvent );
::LeaveCriticalSection( &m_csStateChange );
}
else bresult = false;
}
::ReleaseMutex( m_accessMutex );
return bresult;
} // lock
bool unlock
{
::EnterCriticalSection( &m_csStateChange );
if(m_currentLevel== LOCK_LEVEL_READ )
{
m_readCount --;
if(m_readCount== 0)
{
m_currentLevel = LOCK_LEVEL_NONE;
::SetEvent (m_unlockEvent);
}
}
else if(m_currentLevel== LOCK_LEVEL_WRITE )
{
m_currentLevel = LOCK_LEVEL_NONE;
::SetEvent ( m_unlockEvent );
}
::LeaveCriticalSection( &m_csStateChange );
return true;
}
};