读写锁,也叫共享-独占锁,简单的说就是可以同时有多个读线程,但同一时间只能一个写线程,并且读线程和写线程不能同时存在。怎么自己来实现一个读写锁呢?
直接上代码吧,用临界区实现简单的读写锁。也可以用互斥体、信号量等来实现。
class ReadWriteLock
{
public:
ReadWriteLock(void);
~ReadWriteLock(void);
void readLock();
void writeLock();
void unlock();
static void selfTest();
private:
CRITICAL_SECTION m_csRead;
CRITICAL_SECTION m_csWrite;
int m_readCount;
enum RWLOCK_STATE {
RWLOCK_IDLE,
RWLOCK_READ,
RWLOCK_WRITE
} m_state;
};
#include "StdAfx.h"
#include "ReadWriteLock.h"
#include "process.h"
#include "time.h"
#include <iostream>
#include <string>
using namespace std;
ReadWriteLock::ReadWriteLock(void)
{
m_readCount = 0;
m_state = RWLOCK_IDLE;
InitializeCriticalSection(&m_csRead);
InitializeCriticalSection(&m_csWrite);
}
ReadWriteLock::~ReadWriteLock(void)
{
DeleteCriticalSection(&m_csRead);
DeleteCriticalSection(&m_csWrite);
}
void ReadWriteLock::readLock()
{
EnterCriticalSection(&m_csRead);
++m_readCount;
if (m_readCount == 1) {
EnterCriticalSection(&m_csWrite);
m_state = RWLOCK_READ;
}
LeaveCriticalSection(&m_csRead);
}
void ReadWriteLock::writeLock()
{
EnterCriticalSection(&m_csWrite);
m_state = RWLOCK_WRITE;
}
void ReadWriteLock::unlock()
{
if (m_state == RWLOCK_READ) {
EnterCriticalSection(&m_csRead);
--m_readCount;
if (m_readCount == 0) {
m_state = RWLOCK_IDLE;
LeaveCriticalSection(&m_csWrite);
}
LeaveCriticalSection(&m_csRead);
}
else if (m_state == RWLOCK_WRITE) {
m_state = RWLOCK_IDLE;
LeaveCriticalSection(&m_csWrite);
}
}
struct TestProc {
int m_thread;
bool m_read;
ReadWriteLock *m_rwLock;
};
unsigned static __stdcall lockTestProc(void* pArg)
{
Sleep(rand() % 10 * 1000);
TestProc* pParam = (TestProc*)pArg;
string content = pParam->m_read ? " with read" : " with write";
cout << "###Being thread" << pParam->m_thread << content << endl;
if (pParam->m_read) {
pParam->m_rwLock->readLock();
cout << "Thread" << pParam->m_thread << " is reading" << endl;
Sleep(rand() % 10 * 10);
cout << "Thread" << pParam->m_thread << " read complete" << endl;
pParam->m_rwLock->unlock();
}
else {
pParam->m_rwLock->writeLock();
cout << "Thread" << pParam->m_thread << " is writing" << endl;
Sleep(rand() % 10 * 1000);
cout << "Thread" << pParam->m_thread << " write complete" << endl;
pParam->m_rwLock->unlock();
}
return 0;
}
void ReadWriteLock::selfTest()
{
ReadWriteLock rwLock;
srand(time(NULL));
TestProc procParams[10];
HANDLE handles[10];
for (int i = 0; i < 10; ++i) {
procParams[i].m_rwLock = &rwLock;
procParams[i].m_thread = i;
procParams[i].m_read = rand() % 3 == 0 ? false : true;
handles[i] = (HANDLE)_beginthreadex(NULL, 0, lockTestProc, (void*)&procParams[i], 0, NULL);
Sleep(10);
}
WaitForMultipleObjects(10, handles, true, INFINITE);
for (int i = 0; i < 10; ++i) {
CloseHandle(handles[i]);
}
}
测试结果如下。可以看到这里读线程有较高的优先权,如果读者一直存在,那么写线程将得不到执行。算是所谓的活锁吧,write starvation。
怎么避免这种情况呢?可以给写线程较高的优先级 - 如果有写线程在等待,那么新来的读线程也必须等待。当然根据应用的不同可以有不同的策略,比如读写线程公平竞争等。这里呢给出写线程优先的一种方案。
class WritePreferredRWLock
{
public:
WritePreferredRWLock(void);
~WritePreferredRWLock(void);
void readLock();
void writeLock();
void unlock();
static void selfTest();
private:
CRITICAL_SECTION m_csRead;
CRITICAL_SECTION m_csWrite;
HANDLE m_eventWriteComplete;
int m_readCount;
int m_readWaiting;
int m_writeWaiting;
enum RWLOCK_STATE {
RWLOCK_IDLE,
RWLOCK_READ,
RWLOCK_WRITE
} m_state;
};
#include "StdAfx.h"
#include "WritePreferredRWLock.h"
#include "process.h"
#include "time.h"
#include <iostream>
#include <string>
using namespace std;
WritePreferredRWLock::WritePreferredRWLock(void)
{
m_readCount = 0;
m_readWaiting = 0;
m_writeWaiting = 0;
m_state = RWLOCK_IDLE;
InitializeCriticalSection(&m_csRead);
InitializeCriticalSection(&m_csWrite);
m_eventWriteComplete = CreateEvent(NULL, TRUE, FALSE, NULL);
}
WritePreferredRWLock::~WritePreferredRWLock(void)
{
DeleteCriticalSection(&m_csRead);
DeleteCriticalSection(&m_csWrite);
CloseHandle(m_eventWriteComplete);
}
void WritePreferredRWLock::readLock()
{
EnterCriticalSection(&m_csRead);
if (m_writeWaiting > 0) {
++m_readWaiting;
LeaveCriticalSection(&m_csRead);
while (1) {
WaitForSingleObject(m_eventWriteComplete, INFINITE);
EnterCriticalSection(&m_csRead);
// Check write waiting again to avoid deadlock
if (m_writeWaiting > 0) {
LeaveCriticalSection(&m_csRead);
}
else {
break;
}
}
--m_readWaiting;
++m_readCount;
if (m_readCount == 1) {
EnterCriticalSection(&m_csWrite);
m_state = RWLOCK_READ;
}
}
else {
++m_readCount;
if (m_readCount == 1) {
EnterCriticalSection(&m_csWrite);
m_state = RWLOCK_READ;
}
}
LeaveCriticalSection(&m_csRead);
}
void WritePreferredRWLock::writeLock()
{
EnterCriticalSection(&m_csRead);
++m_writeWaiting;
LeaveCriticalSection(&m_csRead);
EnterCriticalSection(&m_csWrite);
ResetEvent(m_eventWriteComplete);
EnterCriticalSection(&m_csRead);
--m_writeWaiting;
m_state = RWLOCK_WRITE;
LeaveCriticalSection(&m_csRead);
}
void WritePreferredRWLock::unlock()
{
if (m_state == RWLOCK_READ) {
EnterCriticalSection(&m_csRead);
--m_readCount;
if (m_readCount == 0) {
m_state = RWLOCK_IDLE;
LeaveCriticalSection(&m_csWrite);
}
LeaveCriticalSection(&m_csRead);
}
else if (m_state == RWLOCK_WRITE) {
EnterCriticalSection(&m_csRead);
m_state = RWLOCK_IDLE;
if (m_writeWaiting == 0) {
SetEvent(m_eventWriteComplete);
}
LeaveCriticalSection(&m_csRead);
LeaveCriticalSection(&m_csWrite);
}
}
struct TestProc {
int m_thread;
bool m_read;
WritePreferredRWLock *m_rwLock;
};
unsigned static __stdcall lockTestProc(void* pArg)
{
TestProc* pParam = (TestProc*)pArg;
string content = pParam->m_read ? " with read" : " with write";
cout << "###Begin thread" << pParam->m_thread << content << endl;
if (pParam->m_read) {
pParam->m_rwLock->readLock();
cout << "Thread" << pParam->m_thread << " is reading" << endl;
Sleep(rand() % 10 * 1000);
cout << "Thread" << pParam->m_thread << " read complete" << endl;
pParam->m_rwLock->unlock();
}
else {
pParam->m_rwLock->writeLock();
cout << "Thread" << pParam->m_thread << " is writing" << endl;
Sleep(rand() % 10 * 1000);
cout << "Thread" << pParam->m_thread << " write complete" << endl;
pParam->m_rwLock->unlock();
}
return 0;
}
void WritePreferredRWLock::selfTest()
{
const int THREAD_NUM = 10;
WritePreferredRWLock rwLock;
srand(time(NULL));
TestProc procParams[THREAD_NUM];
HANDLE handles[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; ++i) {
procParams[i].m_rwLock = &rwLock;
procParams[i].m_thread = i;
procParams[i].m_read = rand() % 5 == 0 ? false : true;
handles[i] = (HANDLE)_beginthreadex(NULL, 0, lockTestProc, (void*)&procParams[i], 0, NULL);
Sleep(10);
}
WaitForMultipleObjects(THREAD_NUM, handles, true, INFINITE);
for (int i = 0; i < 50; ++i) {
CloseHandle(handles[i]);
}
}
测试结果如下。可以看出这里写线程优先级比较高,一旦有写线程在等待,在所有的写线程执行完之后读线程才会执行。
写线程优先的读写锁需要多次检测,所以性能上会差一些。 注意这里读写锁公用数据的修改用了m_csRead来保护。貌似也能用InterLocked这类函数做到吧。