信号量是个UNIX操作系统级的功能,提供绝对安全的互斥操作。但是如同所有C接口,功能强大,接口复杂,不经包装难以舒适使用。
目录
一、一般程序需要的互斥操作
二、设计一个写优先的读写互斥信号量
2.1 信号量结构
2.2 写锁定的逻辑
2.3 读锁定的逻辑
2.4 释放写锁定的逻辑
2.5 释放读锁定的逻辑
三、完整源码
四、代码详解
大部分应用的互斥操作要求很简单:
第一个要求很容易理解,就是并发操作不能冲突,实现方法就是“锁定-操作-释放”,好一点的系统或函数库都会直接提供方法,用类似“lock()、unlock()”的一对函数即可。但是信号量操作是系统底层功能,不提供简化接口,这就需要我们自己来写。
第二个要求在大型数据处理场合需要。用简单互斥对象时,读写平权,大家都有机会,但是一般来说,我们都希望写操作能尽快完成,因为写操作不完成的话读操作读到的都是旧数据,没有意义,所以我们会设计为写操作优先。极端情形下写操作太频繁导致读操作无法进行,这就只能想别的办法来解决了,比如写操作主动降低点速度,或者干脆读写分开(当然这样就不要互斥了——不用互斥才是我们的最高理想)。
本文不介绍信号量的细节,因为教科书上有,网上也遍地都是。这里给出的是一个实用版本。
为了实现写优先的读写锁,需要4个信号值:
争议:用了四个信号值是不是多了?能不能只用三个?这个不好说,反正现在这个版本已经用了十几年了。实际代码中分了等待和不等待两种操作方法。
为了实现写优先,写锁定需要两步实现:
读锁定必须等待写和写等待都为0。
这是完整源码,比我上面介绍的多一些功能,后面我会逐个讲解功能点。
//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博客
(这里是结束)