多线程之读写锁

读写锁,也叫共享-独占锁,简单的说就是可以同时有多个读线程,但同一时间只能一个写线程,并且读线程和写线程不能同时存在。怎么自己来实现一个读写锁呢?

直接上代码吧,用临界区实现简单的读写锁。也可以用互斥体、信号量等来实现。

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这类函数做到吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值