程序设计:信号量 写优先的读写互斥对象(完整源码 代码详解)

        信号量是个UNIX操作系统级的功能,提供绝对安全的互斥操作。但是如同所有C接口,功能强大,接口复杂,不经包装难以舒适使用。

目录

一、一般程序需要的互斥操作

二、设计一个写优先的读写互斥信号量

2.1 信号量结构

2.2 写锁定的逻辑

2.3 读锁定的逻辑

2.4 释放写锁定的逻辑

2.5 释放读锁定的逻辑

三、完整源码

四、代码详解


一、一般程序需要的互斥操作

        大部分应用的互斥操作要求很简单:

  1. 多进程、多线程读写不冲突
  2. 写优先

        第一个要求很容易理解,就是并发操作不能冲突,实现方法就是“锁定-操作-释放”,好一点的系统或函数库都会直接提供方法,用类似“lock()、unlock()”的一对函数即可。但是信号量操作是系统底层功能,不提供简化接口,这就需要我们自己来写。

        第二个要求在大型数据处理场合需要。用简单互斥对象时,读写平权,大家都有机会,但是一般来说,我们都希望写操作能尽快完成,因为写操作不完成的话读操作读到的都是旧数据,没有意义,所以我们会设计为写操作优先。极端情形下写操作太频繁导致读操作无法进行,这就只能想别的办法来解决了,比如写操作主动降低点速度,或者干脆读写分开(当然这样就不要互斥了——不用互斥才是我们的最高理想)。

        本文不介绍信号量的细节,因为教科书上有,网上也遍地都是。这里给出的是一个实用版本。

二、设计一个写优先的读写互斥信号量

2.1 信号量结构

        为了实现写优先的读写锁,需要4个信号值:

  1. 写资源,初始为1,只允许一个写
  2. 写计数,初始为0,当前在写的个数
  3. 读计数,初始为0,当前在读的个数
  4. 写等待,初始为0,写锁定之前先增加这个值,阻止读操作

        争议:用了四个信号值是不是多了?能不能只用三个?这个不好说,反正现在这个版本已经用了十几年了。实际代码中分了等待和不等待两种操作方法。

2.2 写锁定的逻辑

        为了实现写优先,写锁定需要两步实现:

  1. 增加写锁定,这一步不会等待,立即完成。由于读操作必须等到写等待为0,预先设置写等待就保证之后不会有更多写操作,只需要等待当前已经在进行的写操作完成。
  2. 写资源-1,写计数+1,等待读计数为0,写等待-1
  3. 上一步返回正确表示锁定成功

2.3 读锁定的逻辑

        读锁定必须等待写和写等待都为0。

  1. 等待写计数为0,等待写等待为0,读计数+1
  2. 上一步会返回正确表示锁定成功

2.4 释放写锁定的逻辑

  1. 写资源+1,写计数-1,写等待-1,这一步没有等待,立即完成(除非有BUG,否则减法一定够减)。写等待-1这个操作其实逻辑上应该放在锁定的时候完成。

2.5 释放读锁定的逻辑

  1. 读计数-1,这一步没有等待,立即完成,除非有BUG。

三、完整源码

        这是完整源码,比我上面介绍的多一些功能,后面我会逐个讲解功能点。

//mymutex.h


#ifndef mySTD_myMUTEX_H
#define mySTD_myMUTEX_H

#include 
#include "myfunction.h"
#ifndef USE_BOL
#include 
#include 
#include 
#include 

union semun
{
	int val;
	struct semid_ds* buf;
	unsigned short* array;
};
#else
#include "proc/bol_ipc.h"
#include "pub_def.h"
#define semget bol_ipc_semget
#define semctl bol_ipc_semctl
#define semop bol_ipc_semop
#define semctl bol_ipc_semctl
#endif

namespace ns_my_std_2
{
	//对象实例不可复制不可移动,内部记录进程操作状态和线程操作状态
	class CmyRWMutex1
	{
	private:
		int sem_id{ -1 };//信号量ID
		mutable bool isIngore{ false };//是否忽略,不锁定
		mutable bool isSafe{ false };//是否带有安全检查,确保操作序列正确

		//进程操作计数,防止操作顺序错误
		mutable atomic count_WLock{ 0 };
		mutable atomic 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 d;//通过对象地址区分不同的对象
			return &d[this];
		}

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

		~CmyRWMutex1()
		{
			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 = -1;
		}
	private:
		mutable int m_errid{ 0 };//最近的错误号
		mutable CmyStringStream 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 (sem_id != -1)
			{
				sprintf(buf, "sem_id = %10d , W %d R %d (%s), %s %s", 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:
		bool _GetCTime(time_t& ctime)const
		{
			semid_ds ds;
			union semun sem;
			int ret;

			memset(&ds, 0, sizeof(semid_ds));
			sem.buf = &ds;
			if ((ret = semctl(sem_id, 0, IPC_STAT, sem)) < 0)
			{
				*(int*)&m_errid = __LINE__;
				(*(stringstream*)&m_errmsg).str("");
				*(stringstream*)&m_errmsg << "获得sem " << sem_id << " 状态出错(" << ret << "):" << errno2str();
				return false;
			}
			ctime = ds.sem_ctime;
			return true;
		}

		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; }
		int GetID()const
		{
			return sem_id;
		}
		int GetID(time_t& ctime)const
		{
			if (!_GetCTime(ctime))return -1;
			return sem_id;
		}
		bool Attach(int id, time_t const& ctime)
		{
			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;

			//检查
			time_t tmp_ctime;
			if (!_GetCTime(tmp_ctime))
			{
				m_errid = __LINE__;
				m_errmsg.str("");
				m_errmsg << errno2str();
				sem_id = -1;
				return false;
			}
			else
			{
				if (tmp_ctime != ctime)
				{
					m_errid = __LINE__;
					m_errmsg.str("");
					m_errmsg << "ctime不匹配";
					sem_id = -1;
					return false;
				}
			}

			return -1 != 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 = -1;
			return true;
		}

		//创建新信号量
		bool Create()
		{
			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;
				}
			}
#ifdef _LINUXOS
			sem_id = semget(IPC_PRIVATE, 4, IPC_CREAT | IPC_EXCL | 0666);
#else
			sem_id = semget(IPC_PRIVATE, 4, IPC_CREAT | IPC_EXCL | SEM_R | (SEM_R >> 3) | (SEM_R >> 6) | SEM_A | (SEM_A >> 3) | (SEM_A >> 6));
#endif
			if (-1 == sem_id)
			{
				m_errid = __LINE__;
				m_errmsg.str("");
				m_errmsg << errno2str();
				return false;
			}
			union semun sem;
			sem.val = 1;
			if (semctl(sem_id, 0, SETVAL, sem) < 0)
			{
				m_errid = __LINE__;
				m_errmsg.str("");
				m_errmsg << errno2str();
				return false;
			}
			sem.val = 0;
			if (semctl(sem_id, 1, SETVAL, sem) < 0)
			{
				m_errid = __LINE__;
				m_errmsg.str("");
				m_errmsg << errno2str();
				return false;
			}
			sem.val = 0;
			if (semctl(sem_id, 2, SETVAL, sem) < 0)
			{
				m_errid = __LINE__;
				m_errmsg.str("");
				m_errmsg << errno2str();
				return false;
			}
			sem.val = 0;
			if (semctl(sem_id, 3, SETVAL, sem) < 0)
			{
				m_errid = __LINE__;
				m_errmsg.str("");
				m_errmsg << errno2str();
				return false;
			}
			return true;
		}
		//删除信号量
		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;
				}
			}
			union semun sem;
			sem.val = 0;
			if (semctl(sem_id, 0, IPC_RMID, sem) < 0)
			{
				m_errid = __LINE__;
				m_errmsg.str("");
				m_errmsg << errno2str();
				return false;
			}
			else
			{
				sem_id = -1;
				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;//忽略锁定
			}
			constexpr short int flag = SEM_UNDO;
			constexpr short int flag_nowait = SEM_UNDO | IPC_NOWAIT;

			struct sembuf sops_wait[] = { {1,0,flag},{2,1,flag},{3,0,flag} };
			struct sembuf sops_nowait[] = { {1,0,flag_nowait},{2,1,flag_nowait},{3,0,flag_nowait} };

			struct sembuf* sops = sops_wait;
			if (no_wait)sops = sops_nowait;

			if (-1 != semop(sem_id, sops, 3))
			{
				after_RLock();
				return true;
			}
			else
			{
				*(int*)&m_errid = __LINE__;
				(*(stringstream*)&m_errmsg).str("");
				*(stringstream*)&m_errmsg << errno2str();
				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;//忽略锁定
			}

			STATIC_C short int const flag = SEM_UNDO;
			STATIC_C short int const flag_nowait = SEM_UNDO | IPC_NOWAIT;

			struct sembuf sops_wait[] = { {0,-1,flag},{1,1,flag},{2,0,flag},{3,1,flag} };
			struct sembuf sops_nowait[] = { {0,-1,flag_nowait},{1,1,flag_nowait},{2,0,flag_nowait},{3,1,flag_nowait},{3,-1,flag_nowait} };

			struct sembuf* sops = sops_wait;
			if (no_wait)sops = sops_nowait;

			//先增加写等待数,阻止读请求被激活
			if (-1 == semop(sem_id, &sops[3], 1))
			{
				*(int*)&m_errid = __LINE__;
				(*(stringstream*)&m_errmsg).str("");
				*(stringstream*)&m_errmsg << errno2str();
				return false;
			}
			if (-1 != semop(sem_id, sops, 3))
			{
				after_WLock();
				return true;
			}
			else if (no_wait && EAGAIN == errno)
			{//撤销写等待
				if (-1 == semop(sem_id, &sops[4], 1))
				{
					*(int*)&m_errid = __LINE__;
					(*(stringstream*)&m_errmsg).str("");
					*(stringstream*)&m_errmsg << errno2str();
				}
				return false;
			}
			else
			{
				*(int*)&m_errid = __LINE__;
				(*(stringstream*)&m_errmsg).str("");
				*(stringstream*)&m_errmsg << errno2str();
				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;//忽略锁定
			}
			struct sembuf sops[] = { {2,-1,SEM_UNDO} };
			if (-1 != semop(sem_id, sops, 1))
			{
				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;//忽略锁定
			}
			struct sembuf sops[] = { {0,1,SEM_UNDO},{1,-1,SEM_UNDO},{3,-1,SEM_UNDO} };
			if (-1 != semop(sem_id, sops, 3))
			{
				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 (sem_id != -1)
			{
				union semun sem;
				int val;
				sem.val = 0;
				if ((val = semctl(sem_id, 0, GETVAL, sem)) < 0)
				{
					return false;
				}
				else
				{
					w = val;
				}
				if ((val = semctl(sem_id, 1, GETVAL, sem)) < 0)
				{
					return false;
				}
				else
				{
					w_count = val;
				}
				if ((val = semctl(sem_id, 2, GETVAL, sem)) < 0)
				{
					return false;
				}
				else
				{
					r_count = val;
				}
				if ((val = semctl(sem_id, 3, GETVAL, sem)) < 0)
				{
					return false;
				}
				else
				{
					w_wait = val;
				}
			}
			return true;
		}
		bool GetCTime(time_t& ctime)const
		{
			return _GetCTime(ctime);
		}
	};

}
#endif//mySTD_myMUTEX_H

四、代码详解

        在这里:程序设计:信号量 写优先的读写互斥对象(完整源码 代码详解)2-CSDN博客

(这里是结束)

你可能感兴趣的:(程序员造轮子(我的可重用代码),信号量,互斥锁,读写互斥,写优先)