程序设计:C++11原子 写优先的读写锁(源码详解)

初级代码游戏的专栏介绍与文章目录-CSDN博客

我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。

这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。


github位置:codetoys/ctfc.git src/function/mymutex.h和mymutex1-3.h  

        这是对程序设计:信号量 写优先的读写互斥对象(完整源码 代码详解)-CSDN博客的原子对象版本,写原来那个版本的时候C++11尚未发布。

        关于写优先的读写互斥对象的原理可以参考上面的链接,但使用原子对象比使用信号量更简单,因为我采用的是简单锁加上自行判断的方式。

        本文不详细讲解原子对象,只强调这么几点:

  • 原子对象是面向CPU的,非常晦涩,实际上,我认为这部分的设计是失败的
  • 原子对象的全部功能没有一种CPU是完全实现的,所以,搞清楚也没什么用
  • 对结构的原子实现需要借助隐藏的变量,在Linux上(准确地说大概应该是g++的STL实现)这个隐藏的变量是放在进程的其它位置,而不是直接添加在结构里面,所以只能实现进程内互斥,无法实现跨进程互斥。而在windows上(实际是指VS的STL实现),我推测如果数据放在内存映射文件(相当于UNIX/LINUX的共享内存),是可以跨进程互斥的。
  • 就用最严格的简单互斥就行了,别给自己找麻烦

        鉴于以上几点,我用atomic_flag做互斥控制,其功能就是锁定/解锁,相当于进出一次信号量操作,而信号量里面数值处理在代码里实现。

        为什么一定要用原子来重新实现呢?因为原子的性能比信号量实在是快了太多了。

        相关技术点:

  • C++11的原子功能 头文件<atomic> 在CentOS上并非默认支持,需要安装额外的库
  • C++11的线程功能 头文件<thread> 比POSIX线程库简单太多了
  • atomic_flag 原子的bool,可以用来实现锁定和解锁
  • atomic_flag::test_and_set() 如果是false就设置为true,整个操作是原子的,前后加了锁,不可能被中断和乱序
  • atomic_flag::clear() 设置为false,整个操作也是原子的
  • this_thread::yield() 让出线程时间片。做循环判断时用这个比死循环省CPU、比sleep定时响应快(就是为了实现所谓“自旋锁”,重试等待)

        读写锁代码:

#include <atomic>
#include <thread>

		struct mySEM
		{
		public:
			atomic_flag flag{false};
			time_t ctime{ 0 };

			bool OnW{false};
			long R_count{ 0 };
			long W_wait{ 0 };

		private:
			bool _Lock()
			{
				//cout << (long)this << " _Lock ..." << endl;
				while (flag.test_and_set())
				{
					this_thread::yield();
				}
				//cout << (long)this << "_Lock down" << endl;
				return true;
			}
			bool _UnLock()
			{
				//cout << (long)this << "_Lock release" << endl;
				flag.clear();
				return true;
			}
		public:
			void init()
			{
				flag.clear();
				ctime = time(nullptr);
				OnW = false;
				R_count = 0;
				W_wait = 0;
			}
			bool RLock(bool no_wait)
			{
				_Lock();
				while (!(!OnW && 0 == W_wait))
				{
					_UnLock();
					if (no_wait)return false;
					this_thread::yield();
					_Lock();
				}
				++R_count;
				_UnLock();
				return true;
			}
			bool RUnLock()
			{
				_Lock();
				--R_count;
				_UnLock();
				return true;
			}
			bool WLock(bool no_wait)
			{
				_Lock();
				++W_wait;
				_UnLock();
				
				_Lock();
				while (!(!OnW && 0 == R_count))
				{
					if (no_wait)
					{
						--W_wait;
						_UnLock();
						return false;
					}
					_UnLock();
					this_thread::yield();
					_Lock();
				}
				OnW = true;
				--W_wait;
				_UnLock();
				return true;
			}
			bool WUnLock()
			{
				_Lock();
				OnW = false;
				_UnLock();
				return true;
			}
		};

        解释一下:

atomic_flag flag{false}; 原子对象,用来保护对其它成员变量的操作。

time_t ctime{ 0 }; 创建时间,对功能而言可以无视。

bool OnW{false}; 状态:是否写锁定中。

long R_count{ 0 }; 读计数。

long W_wait{ 0 }; 写等待。

        很容易看出来这个读写锁的逻辑和程序设计:信号量 写优先的读写互斥对象(完整源码 代码详解)-CSDN博客中用信号量的逻辑是一样的。

        私有方法:

bool _Lock()/bool _UnLock() 锁定/解锁原子对象,公有方法必须先锁定才能操作内部成员。

        公有方法:

void init() 初始化,简单测试不需要使用,因为和初值是一样的。

bool RLock(bool no_wait)/bool RUnLock() 读锁定/解锁

bool WLock(bool no_wait)/bool WUnLock() 写锁定/解锁

        感觉比信号量简单多了啊。

        当然实际使用还要考虑如何跟踪调试,所以这个类其实只是实际代码一小部分而已,完整代码如下:

#pragma once

#include <atomic>
#include <thread>


	//对象实例不可复制不可移动,内部记录进程操作状态和线程操作状态
	class CZS_RWMutex2
	{
	public:
		struct mySEM
		{
		public:
			atomic_flag flag{false};
			time_t ctime{ 0 };

			bool OnW{false};
			long R_count{ 0 };
			long W_wait{ 0 };

		private:
			bool _Lock()
			{
				//cout << (long)this << " _Lock ..." << endl;
				while (flag.test_and_set())
				{
					this_thread::yield();
				}
				//cout << (long)this << "_Lock down" << endl;
				return true;
			}
			bool _UnLock()
			{
				//cout << (long)this << "_Lock release" << endl;
				flag.clear();
				return true;
			}
		public:
			void init()
			{
				flag.clear();
				ctime = time(nullptr);
				OnW = false;
				R_count = 0;
				W_wait = 0;
			}
			bool RLock(bool no_wait)
			{
				_Lock();
				while (!(!OnW && 0 == W_wait))
				{
					_UnLock();
					if (no_wait)return false;
					this_thread::yield();
					_Lock();
				}
				++R_count;
				_UnLock();
				return true;
			}
			bool RUnLock()
			{
				_Lock();
				--R_count;
				_UnLock();
				return true;
			}
			bool WLock(bool no_wait)
			{
				_Lock();
				++W_wait;
				_UnLock();
				
				_Lock();
				while (!(!OnW && 0 == R_count))
				{
					if (no_wait)
					{
						--W_wait;
						_UnLock();
						return false;
					}
					_UnLock();
					this_thread::yield();
					_Lock();
				}
				OnW = true;
				--W_wait;
				_UnLock();
				return true;
			}
			bool WUnLock()
			{
				_Lock();
				OnW = false;
				_UnLock();
				return true;
			}
		};
	private:
		mutable mySEM* sem_id{ nullptr };//信号量ID
		mutable bool isIngore{ false };//是否忽略,不锁定
		mutable bool isSafe{ false };//是否带有安全检查,确保操作序列正确

		//进程操作计数,防止操作顺序错误
		mutable atomic<int> count_WLock{ 0 };
		mutable atomic<int> count_RLock{ 0 };

		//线程操作记录,防止线程操作错误并可用于中途让出再重新锁定
		struct thread_data
		{
			bool _isLocked{ false };//是否已经锁定,若已经锁定则不重复锁定
			bool _isWLock{ false };//是否是写锁定,当isLocked时有效

			bool isLocked()const
			{
				return _isLocked;
			}
			bool isWLocked()const
			{
				return _isLocked && _isWLock;
			}
			bool isRLocked()const
			{
				return _isLocked && !_isWLock;
			}
			void thread_data_WLock()
			{
				_isLocked = true;
				_isWLock = true;
			}
			void thread_data_RLock()
			{
				_isLocked = true;
				_isWLock = false;
			}
			void thread_data_UnLock()
			{
				_isLocked = false;
				_isWLock = false;
			}
		};
	public:
		thread_data* getThreadData()const
		{
			thread_local map<CZS_RWMutex2 const*, thread_data > d;//通过对象地址区分不同的对象
			return &d[this];
		}

		//禁止移动和复制(不能用于vector,因为vector会移动对象)
		CZS_RWMutex2() = default;
		CZS_RWMutex2(CZS_RWMutex2 const&) = delete;
		CZS_RWMutex2& operator =(CZS_RWMutex2 const&) = delete;
		CZS_RWMutex2(CZS_RWMutex2 const&&) = delete;
		CZS_RWMutex2& operator =(CZS_RWMutex2 const&&) = delete;

		~CZS_RWMutex2()
		{
			if (0 != count_WLock || 0 != count_RLock)
			{
				if (0 != count_WLock) cout << "警告:析构而未解锁:" << sem_id << " is w locked " << count_WLock << endl;
				if (0 != count_RLock) cout << "警告:析构而未解锁:" << sem_id << " is r locked " << count_RLock << endl;
			}
			sem_id = nullptr;
		}
	private:
		mutable int m_errid{ 0 };//最近的错误号
		mutable CZS_StringStream m_errmsg;//最近的错误信息

		string errno2str()const
		{
			string s;
			switch (errno)
			{
			case EACCES: s = "EACCES"; break;
			case EINVAL: s = "EINVAL"; break;
			case EPERM: s = "EPERM"; break;
			case EOVERFLOW: s = "EOVERFLOW"; break;
			case ERANGE: s = "ERANGE"; break;
			case E2BIG: s = "E2BIG"; break;
			case EAGAIN: s = "EAGAIN"; break;
			case EFAULT: s = "EFAULT"; break;
			case EFBIG: s = "EFBIG"; break;
			case EIDRM: s = "EIDRM"; break;
			case EINTR: s = "EINTR"; break;
			case ENOSPC: s = "ENOSPC"; break;
			default: s = "semctl error";
			}
			return s;
		}
	public:
		string Report()const
		{
			char buf[1024];
			string ret;
			if (nullptr != sem_id)
			{
				sprintf(buf, "sem_id = %10ld , W %d R %d (%s), %s %s", (long)sem_id, count_WLock.load(), count_RLock.load()
					, (getThreadData()->isLocked() ? (getThreadData()->isWLocked() ? "W" : "R") : "-")
					, (isSafe ? "safe" : ""), (isIngore ? " , ingored" : ""));
				ret += buf;

				long w, w_count, r_count, w_wait;
				if (GetCount2(w, w_count, r_count, w_wait))
				{
					sprintf(buf, " 写锁 %ld 写计数 %ld 读计数 %ld 写等待 %ld", w, w_count, r_count, w_wait);
					ret += buf;
				}
				if (0 != m_errid)
				{
					sprintf(buf, " 错误:%d %s", m_errid, m_errmsg.str().c_str());
				}
				else
				{
					sprintf(buf, " 无错误");
				}
				ret += buf;
			}
			else
			{
				ret += "空信号量";
			}
			return ret;
		}
	private:
		void after_WLock()const
		{
			++count_WLock;
			getThreadData()->thread_data_WLock();
		}
		void after_RLock()const
		{
			++count_RLock;
			getThreadData()->thread_data_RLock();
		}
		void after_WUnLock()const
		{
			--count_WLock;
			getThreadData()->thread_data_UnLock();
		}
		void after_RUnLock()const
		{
			--count_RLock;
			getThreadData()->thread_data_UnLock();
		}
	public:
		//忽略锁定调用,不执行锁定
		void ingore()const { isIngore = true; }
		//恢复功能
		void enable()const { isIngore = false; }
		//启用安全检查
		void safe(bool _safe)const { isSafe = _safe; }
		bool isConnected()const { return nullptr != sem_id; }
		bool Attach(mySEM* id)
		{
			if (isSafe)
			{
				if (0 != count_WLock || 0 != count_RLock)
				{
					*(int*)&m_errid = __LINE__;
					(*(stringstream*)&m_errmsg).str("");
					*(stringstream*)&m_errmsg << "sem " << sem_id << " 已经锁定,需要先解锁";
					return false;
				}
			}
			sem_id = id;

			return nullptr != sem_id;
		}
		bool Detach()
		{
			if (isSafe)
			{
				if (0 != count_WLock || 0 != count_RLock)
				{
					*(int*)&m_errid = __LINE__;
					(*(stringstream*)&m_errmsg).str("");
					*(stringstream*)&m_errmsg << "sem " << sem_id << " 已经锁定,需要先解锁";
					return false;
				}
			}
			sem_id = nullptr;
			return true;
		}

		//创建新信号量
		bool Create(mySEM * id)
		{
			if (isSafe)
			{
				if (0 != count_WLock || 0 != count_RLock)
				{
					*(int*)&m_errid = __LINE__;
					(*(stringstream*)&m_errmsg).str("");
					*(stringstream*)&m_errmsg << "sem " << sem_id << " 已经锁定,需要先解锁";
					return false;
				}
			}
			sem_id = id;
			if (nullptr == sem_id)
			{
				m_errid = __LINE__;
				m_errmsg.str("");
				m_errmsg << errno2str();
				return false;
			}
			sem_id->init();
			return true;
		}
		//复位
		bool Reset()
		{
			return Create(sem_id);
		}
		//删除信号量
		bool Destory()
		{
			if (isSafe)
			{
				if (0 != count_WLock || 0 != count_RLock)
				{
					*(int*)&m_errid = __LINE__;
					(*(stringstream*)&m_errmsg).str("");
					*(stringstream*)&m_errmsg << "sem " << sem_id << " 已经锁定,需要先解锁";
					return false;
				}
			}

			sem_id = nullptr;
			return true;
		}
		//锁定,等待
		bool RLock()const { return _RLock(false); }
		bool TryRLock()const { return _RLock(true); }
		bool _RLock(bool no_wait)const
		{
			if (isSafe)
			{
				if (getThreadData()->isLocked())
				{
					*(int*)&m_errid = __LINE__;
					(*(stringstream*)&m_errmsg).str("");
					*(stringstream*)&m_errmsg << "sem " << sem_id << " 已经锁定,不能重复锁定";
					return false;
				}
			}
			if (isIngore)
			{
				after_RLock();
				return true;//忽略锁定
			}
			if (sem_id->RLock(no_wait))
			{
				after_RLock();
				return true;
			}
			else
			{
				return false;
			}
		}
		bool WLock()const { return _WLock(false); }
		bool TryWLock()const { return _WLock(true); }
		bool _WLock(bool no_wait)const
		{
			if (isSafe)
			{
				if (getThreadData()->isLocked())
				{
					*(int*)&m_errid = __LINE__;
					(*(stringstream*)&m_errmsg).str("");
					*(stringstream*)&m_errmsg << "sem " << sem_id << " 已经锁定,需要先解锁";
					return false;
				}
			}
			if (isIngore)
			{
				after_WLock();
				return true;//忽略锁定
			}

			if (sem_id->WLock(no_wait))
			{
				after_WLock();
				return true;
			}
			else
			{
				return false;
			}
		}
		//解除锁定
		bool RUnLock()const
		{
			if (isSafe)
			{
				if (!getThreadData()->isRLocked())
				{
					*(int*)&m_errid = __LINE__;
					(*(stringstream*)&m_errmsg).str("");
					*(stringstream*)&m_errmsg << "sem " << sem_id << " 未锁定或不是读锁定";
					return false;
				}
			}
			if (isIngore)
			{
				after_RUnLock();
				return true;//忽略锁定
			}
			if (sem_id->RUnLock())
			{
				after_RUnLock();
				return true;
			}
			else
			{
				*(int*)&m_errid = __LINE__;
				(*(stringstream*)&m_errmsg).str("");
				*(stringstream*)&m_errmsg << errno2str();
				return false;
			}
		}
		bool WUnLock()const
		{
			if (isSafe)
			{
				if (!getThreadData()->isWLocked())
				{
					*(int*)&m_errid = __LINE__;
					(*(stringstream*)&m_errmsg).str("");
					*(stringstream*)&m_errmsg << "sem " << sem_id << " 未锁定或不是写锁定";
					return false;
				}
			}
			if (isIngore)
			{
				after_WUnLock();
				return true;//忽略锁定
			}
			if (sem_id->WUnLock())
			{
				after_WUnLock();
				return true;
			}
			else
			{
				*(int*)&m_errid = __LINE__;
				(*(stringstream*)&m_errmsg).str("");
				*(stringstream*)&m_errmsg << errno2str();
				return false;
			}
		}

		string GetErrorMessage()const { return m_errmsg.str(); }
		int GetErrorID()const { return m_errid; }//获得最新的错误ID
		bool isFree()const
		{
			bool ignored;
			long w_count;
			long r_count;
			long w_wait;
			if (GetCount(ignored, w_count, r_count, w_wait))
			{
				return 0 == w_count + r_count + w_wait;
			}
			return false;
		}
		bool GetCount(bool& ignored, long& w_count, long& r_count, long& w_wait)const
		{
			long w;
			ignored = isIngore;
			return GetCount2(w, w_count, r_count, w_wait);
		}
		bool GetCount2(long& w, long& w_count, long& r_count, long& w_wait)const
		{
			w = 0;
			w_count = 0;
			r_count = 0;
			w_wait = 0;
			if (nullptr != sem_id)
			{
				w = !sem_id->OnW;
				w_count = sem_id->OnW;
				r_count = sem_id->R_count;
				w_wait = sem_id->W_wait;
			}
			return true;
		}
	};


        这部分主要是加入了跟踪调试的功能,具体解释在这里:

        程序设计:C++11原子 写优先的读写锁(源码详解二:操作跟踪)-CSDN博客

(这里是结束)

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C11标准库中提供了读写锁,可以用来实现多个线程对同一资源的并发读操作。下面是使用读写锁的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <threads.h> int data = 0; rwlock_t lock; int reader(void* arg) { int id = *(int*)arg; while (1) { thrd_sleep((struct timespec){.tv_sec = 1}, NULL); // 模拟读操作 // 加读锁 rwlock_rdlock(&lock); printf("Reader %d read data: %d\n", id, data); // 释放读锁 rwlock_unlock(&lock); } return 0; } int writer(void* arg) { int id = *(int*)arg; while (1) { thrd_sleep((struct timespec){.tv_sec = 2}, NULL); // 模拟操作 // 加锁 rwlock_wrlock(&lock); data++; printf("Writer %d write data: %d\n", id, data); // 释放锁 rwlock_unlock(&lock); } return 0; } int main(int argc, char const *argv[]) { // 初始化读写锁 rwlock_init(&lock); // 创建3个读线程 thrd_t readers[3]; int readerIds[3]; for (int i = 0; i < 3; i++) { readerIds[i] = i + 1; thrd_create(&readers[i], reader, &readerIds[i]); } // 创建2个线程 thrd_t writers[2]; int writerIds[2]; for (int i = 0; i < 2; i++) { writerIds[i] = i + 1; thrd_create(&writers[i], writer, &writerIds[i]); } // 等待所有线程结束 for (int i = 0; i < 3; i++) { thrd_join(readers[i], NULL); } for (int i = 0; i < 2; i++) { thrd_join(writers[i], NULL); } // 销毁读写锁 rwlock_destroy(&lock); return 0; } ``` 这个示例程序中有3个读线程和2个线程,读线程每隔1秒钟读一次共享变量data的值,线程每隔2秒钟一次共享变量data的值。读线程和线程之间会相互竞争读写锁,以实现对共享变量的并发访问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值