我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。
这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。
目录
一、演示代码
演示代码和之前的非常相似:
#include "shmSet_Mutex.h"
class CTest_T_SHMSET
{
public:
struct DemoData : public CActiveObjectBase
{
typedef DemoData T_ME;
long n = 0;
sstring<8> s;
//用于需要排序的场合
bool operator < (T_ME const& tmp)const { return n < tmp.n; }
//某些场合也需要等于
bool operator == (T_ME const& tmp)const { return n == tmp.n; }
friend ostream& operator << (ostream& o, T_ME const& d)
{
return o << d.n << " " << d.s.c_str();
}
//关键字的hash值,用于分块场合,应保证hash值的最后一部分仍然是平均分布的
long keyhash()const { return n; }
//用于输出数据的场合
string& toString(string& str)const
{
char buf[2048];
sprintf(buf, "%ld %s", n, s.c_str());
return str = buf;
}
//用于表格输出
static bool AddTableColumns(CHtmlDoc::CHtmlTable2& table)
{
table.AddCol("N", CHtmlDoc::CHtmlDoc_DATACLASS_RIGHT);
table.AddCol("S", CHtmlDoc::CHtmlDoc_DATACLASS_RIGHT);
return true;
}
bool AddTableData(CHtmlDoc::CHtmlTable2& table)const
{
table.AddData(n);
table.AddData(s.c_str());
return true;
}
};
typedef T_SHMSET<DemoData, PI_TEST_1, CDemoData > T_CONTINER;//迭代器和数据操作都是互斥的,但连接断开等管理类操作不是互斥的(通常也不应该并发操作)
//typedef T_SHMSET_MUTEX<DemoData, PI_TEST_1, CDemoData > T_CONTINER;//用这个也可以,但只有M开头的操作才是互斥的,定义在shmSet.h,名字有点混乱
static int test_T_SHMSET(int argc, char** argv)
{
T_CONTINER a("test", 1);
a.DestoryShm();
if (!a.CreateShm())return __LINE__;
thelog << endi;
if (!a.Attach(false))return __LINE__;
thelog << endi;
for (int i = 0; i < 10; ++i)
{
DemoData tmp;
tmp.n = rand() % 10;
tmp.s = "abc";
thelog << i << " n:" << tmp.n << " handle:" << a.insert(tmp).first.handle << endi;
}
for (T_CONTINER::const_iterator it = a.begin(); it != a.end(); ++it)
{
string str;
thelog << it->toString(str) << endi;
}
for (int i = 0; i < 10; ++i)
{
DemoData tmp;
tmp.n = i;
T_CONTINER::const_iterator it = a.find(tmp);
string str;
if (it != a.end())thelog << i << " 找到 handle:" << it.handle << " " << it->toString(str) << endi;
else thelog << i << " 没找到 handle:" << it.handle << endi;
}
a.RunCmdUI();
return 0;
}
};
其实就是稍微改了一下,用两个typedef来简化代码编写:
typedef T_SHMSET<DemoData, PI_TEST_1, CDemoData > T_CONTINER;
写模板经常要重复使用长长的类型定义,用typedef可以极大地简化,并减少BUG(比如漏改了一处导致的几大页的不知所云的编译报错)。不过typedef不能解决类定义里面构造函数必须和类名相同的问题。
演示代码的编译运行和之前也没有什么区别,除了命令是22。运行输出也没什么区别。
演示代码提示另外一个模板类也可以使用相同的测试代码,不过它们的互斥层内部实现是完全不同的。
二、互斥层的实现
2.1 简单的互斥层实现
先看一下简单的互斥层实现,就是演示代码里注释掉的那行使用的模板类。
基本思路是增加一个互斥对象,重新写一组接口,内部用互斥保护:
private:
CManagedMutex m_mutex;
public://互斥操作,全部以M为前缀
//清理全部数据
bool MClear()
{
bool ret=true;
if(m_mutex.WLock())
{
clear();
}
else
{
thelog<<GetName()<<"互斥失败: "<<m_mutex.GetErrorMessage()<<ende;
return false;
}
if(!m_mutex.WUnLock())
{
thelog<<GetName()<<"解锁失败 : "<<m_mutex.GetErrorMessage()<<ende;
return false;
}
return ret;
}
//报告内容,调试用
string & MReport(string & str)const;
//直接存取
bool MGet(T_DATA & value)const;
bool MUpdate(T_DATA const & value);
bool MErase(T_DATA const & value);
这种思路其实相当于业务层自己做互斥。好处是非常可控。
这个模板类为什么能共享之前的测试代码呢?因为是public继承自非互斥的基础模板的。因为测试代码并没有调用M开头的带互斥的代码,所以测试代码并不是多进程、多线程安全的。
但是为什么要public继承基础模板呢?因为有太多公共功能要用了。
所以这里的接口设计应该是有些问题的,可以再想想。(后来我在一部分代码里面用getInterface()的方式来访问公共功能)
2.2 完整互斥接口的实现
2.2.1 互斥对象放在哪里
因为要做完整互斥,不仅类的操作要互斥,迭代器也要互斥。迭代器如何访问互斥对象呢?
因为与共享内存相关的互斥对象在系统内也是唯一的,所以也采用全局变量的形式在存储,用共享内存的参数PI来区分。具体实现是下面几个部分:
#define GET_PP_SET(n) (g_shm_private_data_s[n].pSET)//获得SET对象指针
T_SHMSET * pSet=(T_SHMSET*)GET_PP_SET(PI_N);
if(pSet->m_mutex.RLock())。。。。。。
2.2.2 迭代器的互斥
迭代器的涉及到数据的操作都需要互斥,而且整个迭代器都需要重新定义:
struct iterator : public T_SET::iterator
{
using T_SET::iterator::handle;
iterator(){handle=-1;}
iterator(typename T_SET::iterator it){handle=it.handle;}
iterator & operator=(typename T_SET::iterator const & it){handle=it.handle;return *this;}
iterator & operator ++ ()
{
T_SHMSET * pSet=(T_SHMSET*)GET_PP_SET(PI_N);
if(pSet->m_mutex.RLock())
{//thelog<<"锁定"<<endi;
iterator old=*this;
*this=pSet->T_SET::lower_bound(**this);
if(*this!=pSet->end())
{
if(**this<*old || *old<**this)
{
}
else
{
T_SET::iterator::operator ++();
}
}
}
else
{
thelog<<pSet->GetName()<<"互斥失败: "<<pSet->m_mutex.GetErrorMessage()<<ende;
handle=-1;
return *this;
}
if(!pSet->m_mutex.RUnLock())
{
thelog<<pSet->GetName()<<"解锁失败 : "<<pSet->m_mutex.GetErrorMessage()<<ende;
handle=-1;
return *this;
}//thelog<<"解锁"<<endi;
return *this;
}
iterator & operator -- ()
{
T_SHMSET * pSet=(T_SHMSET*)GET_PP_SET(PI_N);
if(pSet->m_mutex.RLock())
{//thelog<<"锁定"<<endi;
iterator old=*this;
*this=pSet->T_SET::lower_bound(**this);
if(*this!=pSet->end())
{
T_SET::iterator::operator --();
}
}
else
{
thelog<<pSet->GetName()<<"互斥失败: "<<pSet->m_mutex.GetErrorMessage()<<ende;
handle=-1;
return *this;
}
if(!pSet->m_mutex.RUnLock())
{
thelog<<pSet->GetName()<<"解锁失败 : "<<pSet->m_mutex.GetErrorMessage()<<ende;
handle=-1;
return *this;
}//thelog<<"解锁"<<endi;
return *this;
}
};
2.2.3 方法的互斥
所有可能造成冲突的方法都需要互斥。
begin的互斥:
const_iterator begin()const
{
const_iterator ret=end();
T_SHM_SET_R_LOCK_BEGIN;
ret=T_SET::begin();
T_SHM_SET_R_LOCK_END;
}
用到几个宏:
#define _T_SHM_SET_R_LOCK_BEGIN \
if(m_mutex.RLock())\
{
#define _T_SHM_SET_R_LOCK_END \
}\
else\
{\
thelog<<GetName()<<"互斥失败: "<<m_mutex.GetErrorMessage()<<ende;\
return ret;\
}\
\
if(!m_mutex.RUnLock())\
{\
thelog<<GetName()<<"解锁失败 : "<<m_mutex.GetErrorMessage()<<ende;\
return ret;\
}\
return ret;
其实跟迭代器互斥的差不多,就是为了简化代码。
insert的互斥:
pair<iterator, bool> insert(T_DATA const & data)
{
pair<iterator, bool> ret;
pair<typename T_SET::iterator, bool> tmp;
ret.first=end();
ret.second=false;
T_SHM_SET_W_LOCK_BEGIN;
tmp=T_SET::insert(data);
ret.first=tmp.first;
ret.second=tmp.second;
T_SHM_SET_W_LOCK_END;
}
用了两外两个宏,可以在源代码文件里看到。
三、互斥层的设计思想
互斥层不论是基于业务的还是基于通用接口的,都应该严格写成一个独立的层,层内不可以互相调用(一旦互相调用,极易死锁)。
虽然有时候可能在个特殊条件下使用非互斥层来提高速度,但是一定要明显区分互斥层和非互斥层,避免互斥层和非互斥层交替调用。
(这里是文档结束)