多个线程产生race condition,根据业务逻辑,分为共享资源的读和写,多个线程的读不会对数据造成破坏。如果不区分读和写,每次遇到race condition便锁住不允许其他线程访问,造成效率低下。根据以上逻辑,可以用信号量来实现多个线程的共享读,互斥写。
//读写递归锁
class RWRecLock
{
public:
typedef AutoRLockT<RWRecLock> AutoRLock;
typedef AutoWLockT<RWRecLock> AutoWLock;
RWRecLock();
~RWRecLock();
void readLock();
bool tryReadLock();
void writeLock();
bool tryWriteLock();
void unlock();
private:
/// <summary>
/// 用于保护所有的其他成员变量,这样,对它们的操作就能够以原子操作方式来完成
/// </summary>
CRITICAL_SECTION m_cs;
/// <summary>
/// 当许多线程调用readLock,但是由于m_nActive是- 1而被拒绝访问时,
/// 所有阅读线程均等待该信标。当最后一个正在等待的写入线程调用unlock时,
/// 该信标被释放,其数量是m_nWaitingReaders,从而唤醒所有正在等待的阅
/// 读线程
/// </summary>
HANDLE m_hsemReaders;
/// <summary>
/// 当线程调用writeLock,但是由于m_nActive大于0而被拒绝访问时,所
/// 有写入线程均等待该信标。当一个线程正在等待时,新阅读线程将被拒绝
/// 访问该资源。这可以防止阅读线程垄断该资源。当最后一个拥有资源访问
/// 权的阅读线程调用unlock时,该信标就被释放,其数量是1,从而唤醒一个正
/// 在等待的写入线程
/// </summary>
HANDLE m_hsemWriters;
/// <summary>
/// 表示想要访问资源的阅读线程的数量。该值被初始化为0,当m_nActive
/// 是-1时,每当线程调用一次readLock,该值就递增1
/// </summary>
int m_nWaitingReaders;
/// <summary>
/// 表示想要访问资源的写入线程的数量。该值被初始化为0,当m_nActive大
/// 于0时,每当线程调用一次WaitToWrite,该值就递增1
/// </summary>
int m_nWaitingWriters;
/// <summary>
/// 用于反映共享资源的当前状态。如果该值是0,那么没有线程在访问资源。
/// 如果该值大于0,这个值用于表示当前读取该资源的线程的数量。如果这个
/// 数量是负值,那么写入程序正在将数据写入该资源。唯一有效的负值是- 1
/// </summary>
int m_nActive;
};
RWRecLock::RWRecLock()
{
m_nWaitingReaders = 0;
m_nWaitingWriters = 0;
m_nActive = 0;
m_hsemReaders = CreateSemaphore(NULL,0,MAXLONG,NULL);
SORBA_ASSERT(m_hsemReaders);
m_hsemWriters = CreateSemaphore(NULL,0,MAXLONG,NULL);
SORBA_ASSERT(m_hsemWriters);
InitializeCriticalSection(&m_cs);
}
RWRecLock::~RWRecLock()
{
#ifdef _DEBUG
if(m_nActive != 0)
{
SORBA_ASSERT(false);
}
#endif
CloseHandle(m_hsemReaders);
CloseHandle(m_hsemWriters);
DeleteCriticalSection(&m_cs);
}
void RWRecLock::readLock()
{
EnterCriticalSection(&m_cs);
bool bWritePending = (m_nWaitingWriters || (m_nActive<0));
if (bWritePending)
{
++m_nWaitingReaders;
}
else
{
++m_nActive;
}
if(bWritePending)
{
LeaveCriticalSection(&m_cs);
WaitForSingleObject(m_hsemReaders,INFINITE);
return;
}
LeaveCriticalSection(&m_cs);
}
bool RWRecLock::tryReadLock()
{
return false;
}
void RWRecLock::writeLock()
{
EnterCriticalSection(&m_cs);
bool bResourceOwned = (m_nActive != 0);
if(bResourceOwned)
{
++m_nWaitingWriters;
}
else
{
m_nActive = -1;
}
if(bResourceOwned)
{
LeaveCriticalSection(&m_cs);
WaitForSingleObject(m_hsemWriters,INFINITE);
return;
}
LeaveCriticalSection(&m_cs);
}
bool RWRecLock::tryWriteLock()
{
return false;
}
void RWRecLock::unlock()
{
EnterCriticalSection(&m_cs);
SORBA_ASSERT(m_nActive!=0);
/*
std::cout<<"m_nActive:"<<m_nActive<<std::endl;
std::cout<<"m_nWaitingWriters:"<<m_nWaitingWriters<<std::endl;
std::cout<<"m_nWaitingReaders:"<<m_nWaitingReaders<<std::endl;
*/
if(m_nActive > 0)
{
--m_nActive;
}
else
{
++m_nActive;
}
HANDLE hsem = NULL;
LONG lCount = 1;
if(m_nActive == 0)
{
//写线程优先
if(m_nWaitingWriters > 0)
{
m_nActive = -1;
--m_nWaitingWriters;
hsem = m_hsemWriters;
lCount = 1;
}
else if(m_nWaitingReaders >0)
{
m_nActive = m_nWaitingReaders;
m_nWaitingReaders = 0;
hsem = m_hsemReaders;
lCount = m_nActive;
}
/*
//读线程优先,会导致写线程饿死
if(m_nWaitingReaders >0)
{
m_nActive = m_nWaitingReaders;
m_nWaitingReaders = 0;
hsem = m_hsemReaders;
lCount = m_nActive;
}
else if(m_nWaitingWriters > 0)
{
m_nActive = -1;
--m_nWaitingWriters;
hsem = m_hsemWriters;
lCount = 1;
}
*/
else
{
//do nothing
}
}
if(hsem != NULL)
{
LeaveCriticalSection(&m_cs);
if(!ReleaseSemaphore(hsem,lCount,NULL))
{
SORBA_ASSERT(false);
}
return;
}
LeaveCriticalSection(&m_cs);
}
后记:读写锁的缺陷——当读写都在等待的时候,写锁优先获得,倘若写锁频繁申请,将导致读锁一直不能得到,从而造成“读饿死”。
//写线程优先
if(m_nWaitingWriters > 0)
{
m_nActive = -1;
--m_nWaitingWriters;
hsem = m_hsemWriters;
lCount = 1;
}
else if(m_nWaitingReaders >0)
{
m_nActive = m_nWaitingReaders;
m_nWaitingReaders = 0;
hsem = m_hsemReaders;
lCount = m_nActive;
}
/*
//读线程优先,会导致写线程饿死
if(m_nWaitingReaders >0)
{
m_nActive = m_nWaitingReaders;
m_nWaitingReaders = 0;
hsem = m_hsemReaders;
lCount = m_nActive;
}
else if(m_nWaitingWriters > 0)
{
m_nActive = -1;
--m_nWaitingWriters;
hsem = m_hsemWriters;
lCount = 1;
}
*/
else
{
//do nothing
}