无锁编程:c++11基于atomic实现共享读写锁(写优先)

在多线程状态下,对一个对象的读写需要加锁,基于CAS指令的原子语句可以实现高效的线程间协调。关于CAS的概念参见下面的文章:

无锁编程以及CAS

在c++11中CAS指令已经被封装成了 非常方便使用的atomic模板类, 详情参见:

atomic参考

以下代码利用atomic实现了一个读写资源锁,并且可以根据需要通过构造函数参数设置成写优先(write_first)(代码在gcc5和vs2015下编译通过):

readLock/Unlock 实现共享的读取加/解锁,线程数不限,有读取线程工作时,所有的申请写入线程都会等待
writeLock/Unlock 实现独占的写入加/解锁,同时只允许一个线程写入,当有线程在读取时,写入线程等待,当写入线程执行时,所有的读取线程都被等待。

locck/unlock语句允许嵌套
比如

lock.readLock();
lock.readLock();
...
lock.readUnlock();
lock.readUnlock();

也允许在写入状态下嵌套读取,比如

lock.writeLock();
lock.writeLock();
lock.readLock();
...
lock.readUnlock();
lock.writeUnlock();
lock.writeUnlock();

RWLock.h

#include <cstdlib>
#include <cassert>
#include <atomic>
#include <thread>
#include "raii.h"
/*
 * atomic实现读写资源锁,独占写,共享读,禁止复制构造函数和'='赋值操作符
 * WRITE_FIRST为true时为写优先模式,如果有线程等待读取(m_writeWaitCount>0)则等待,优先让写线程先获取锁
 * 允许嵌套加锁
 * readLock/Unlock 实现共享的读取加/解锁,线程数不限
 * writeLock/Unlock 实现独占的写入加/解锁,同时只允许一个线程写入,
 * 当有线程在读取时,写入线程阻塞,当写入线程执行时,所有的读取线程都被阻塞。
 */
class RWLock {
#define WRITE_LOCK_STATUS -1
#define FREE_STATUS 0
private:
	/* 初始为0的线程id */
	static const  std::thread::id NULL_THEAD;
	const bool WRITE_FIRST;
	/* 用于判断当前是否是写线程 */
	thread::id m_write_thread_id;
	/* 资源锁计数器,类型为int的原子成员变量,-1为写状态,0为自由状态,>0为共享读取状态 */
	atomic_int m_lockCount;
	/* 等待写线程计数器,类型为unsigned int的原子成员变量*/
	atomic_uint m_writeWaitCount;
public:
	// 禁止复制构造函数
	RWLock(const RWLock&) = delete;
	// 禁止对象赋值操作符
	RWLock& operator=(const RWLock&) = delete;
	//RWLock& operator=(const RWLock&) volatile = delete;
	RWLock(bool writeFirst=false);;//默认为读优先模式
	virtual ~RWLock()=default;
	int readLock();
	int readUnlock();
	int writeLock();
	int writeUnlock();
	// 将读取锁的申请和释放动作封装为raii对象,自动完成加锁和解锁管理
	raii read_guard()const noexcept{
		return make_raii(*this,&RWLock::readUnlock,&RWLock::readLock);
	}
	// 将写入锁的申请和释放动作封装为raii对象,自动完成加锁和解锁管理
	raii write_guard()noexcept{
		return make_raii(*this,&RWLock::writeUnlock,&RWLock::writeLock);
	}
};

RWLock.cpp


RWLock::RWLock(bool writeFirst):
	WRITE_FIRST(writeFirst),
	m_write_thread_id(),
	m_lockCount(0),
	m_writeWaitCount(0){
}
int RWLock::readLock() {
	// ==时为独占写状态,不需要加锁
	if (this_thread::get_id() != this->m_write_thread_id) {
		int count;
		if (WRITE_FIRST)//写优先模式下,要检测等待写的线程数为0(m_writeWaitCount==0)
			do {
				while ((count = m_lockCount) == WRITE_LOCK_STATUS || m_writeWaitCount > 0);//写锁定时等待
			} while (!m_lockCount.compare_exchange_weak(count, count + 1));
		else
			do {
				while ((count = m_lockCount) == WRITE_LOCK_STATUS); //写锁定时等待
			} while (!m_lockCount.compare_exchange_weak(count, count + 1));
	}
	return m_lockCount;
}
int RWLock::readUnlock() {
	// ==时为独占写状态,不需要加锁
	if (this_thread::get_id() != this->m_write_thread_id)
			--m_lockCount;
	return m_lockCount;
}
int RWLock::writeLock(){
	// ==时为独占写状态,避免重复加锁
	if (this_thread::get_id() != this->m_write_thread_id){
		++m_writeWaitCount;//写等待计数器加1
		// 没有线程读取时(加锁计数器为0),置为-1加写入锁,否则等待
		for(int zero=FREE_STATUS;!this->m_lockCount.compare_exchange_weak(zero,WRITE_LOCK_STATUS);zero=FREE_STATUS);
		--m_writeWaitCount;//获取锁后,计数器减1
		m_write_thread_id=this_thread::get_id();
	}
	return m_lockCount;
}
int RWLock::writeUnlock(){
	if(this_thread::get_id() != this->m_write_thread_id){
		throw runtime_error("writeLock/Unlock mismatch");
	}
	assert(WRITE_LOCK_STATUS==m_lockCount);
	m_write_thread_id=NULL_THEAD;
	m_lockCount.store(FREE_STATUS);
	return m_lockCount;
}
const std::thread::id RWLock::NULL_THEAD;

说明1

atomic_int,atomic_uint 都是从atomic类模板中派生出来的类,对应不同的数据类型

atomic是c++11标准,在gcc编译的时候必须加入std=c++11选项才能正确编译,vs编译至少要用vs2012,因为visual studio 2012以上才支持atomic模板

说明2

如果按照默认的类定义方法,提供复制构造函数和赋值操作符=,那么可以想见,在应用中可能会产生不可预知的问题,所以参照atomic模板的写法,加入了禁止复制构造函数和对象复制操作符=的代码,

	//禁止复制构造函数
	RWLock(const RWLock&) = delete;
	//禁止对象赋值操作符
	RWLock& operator=(const RWLock&) = delete;
	RWLock& operator=(const RWLock&) volatile = delete;

说明3

这个代码还有欠缺的地方就是没有实现超时异常中止。

说明4

read_guard,write_guard函数返回的raii类参见我的另一篇博客《C++11实现模板化(通用化)RAII机制》

  • 6
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
std::atomicC++11引入的一个原子类型,用于实现多线程环境下的原子操作。它提供了一种线程安全的方式来访问和修改共享变量。 std::atomic<T>的内部实现可以使用不同的机制,具体取决于编译器和平台。一种常见的实现方式是使用硬件提供的原子指令来实现原子操作。这些指令可以确保在多线程环境下对共享变量的操作是原子的,即不会被其他线程中断。 另一种实现方式是使用互斥来保护共享变量的访问。当一个线程要访问共享变量时,它会先获取互斥,然后执行操作,最后释放互斥。这种方式可以确保在任意时刻只有一个线程能够访问共享变量,从而避免了竞争条件。 无论使用哪种实现方式,std::atomic都提供了一系列的成员函数来进行原子操作,包括load、store、exchange、compare_exchange等。这些函数可以保证对共享变量的操作是原子的,并且提供了不同的内存序(memory order)选项来控制操作的顺序和可见性。 下面是一个示例代码,演示了如何使用std::atomic进行原子操作: ```cpp #include <iostream> #include <atomic> std::atomic<int> counter(0); void increment() { counter.fetch_add(1, std::memory_order_relaxed); } int main() { std::cout << "Counter: " << counter.load() << std::endl; std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "Counter: " << counter.load() << std::endl; return 0; } ``` 这段代码创建了一个std::atomic<int>类型的counter变量,并定义了一个increment函数,该函数使用fetch_add函数对counter进行原子加一操作。在主函数中,我们创建了两个线程来同时调用increment函数,最后输出counter的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值