软件开发中,经常需要使用到进程通信,数据量大了共享内存便是比较好德选择。但是共享内存一般需要信号量配合实现共享内存同步。
再qt中,共享内存被封装为QSharedMemory类,而信号量被封装为QSystemSemaphore类,底层实现看源代码都是调用标准库接口。
而本文就是在QSharedMemory与QSystemSemaphore的基础上继续进行一次封装,从而实现共享内存及其同步的功能。
gitee项目代码托管地址:https://gitee.com/jiangtao008/shareMemory.git
共享内存文章:https://blog.csdn.net/weixin_42887343/article/details/118416994
共享内存一般存在读取与写入端,我对读取与写入做了一下分类,读写各分成两中模式,如下:
本小结将对其进行分析。
信号量自旋等待
当一个进程试图去获取一个已经锁定的信号量时,该线程不会像自旋锁一样在自旋忙等待,而是会将自身加入一个等待队列中去睡眠,直到其他进程或线程释放信号量后,处于等待队列中的进程才会被唤醒。
信号量释放
释放信号量,用于唤醒其他进程或线程。
直接写入就是直接写入…即向共享内存中写入数据的时候直接写即可,不需要考虑到有进程在读取共享内存(有共享内存锁,无信号量)。
共享内存写入后,需要释放信号量,让其他进程读取。
约定写入即需要向读取写入双方都需要约定好。在没有写入端写入前,读取端不能读取,反过来,在读取前也不能写入。
所以写入内存前需要信号量自旋等待,等待读取进程读取内存后释放信号量释。
写入内存后同样需要释放另外一个信号量(和上面的信号量自旋等待不是一个信号量)。
最基本的就是条件读取模式,需要等待写入信号量释放后再读取。
如果直接读取,一个是进程也不知道内存是否被写入,不断重复读取回浪费cpu资源,第二就是定时读取不能达到内存同步的实时性(写入内存后,可能需要等待n毫秒后数据才被读取),所以最好的方法就是等待信号量释放后立马读取。
约定读取是用于配合约定写入而设计的模式,写入端在使用约定写入模式下,读取端读取内存后必须要释放对应的信号量,否则会导致写入端写入数据时一直等待。
en en en en en en
头文件
#ifndef SYNSHAREMEM_H
#define SYNSHAREMEM_H
#include
#include
#include
#include
#include
class SynShareMem : public QThread
{
Q_OBJECT
public:
enum Type
{
eWrite_1_sema = 1, //无条件写入
eWrite_2_sema = 2, //约定写入(等待其他进程读取后才能写入)
eRead_1_sema = 3, //条件读取
eRead_2_sema = 4 //约定读取(读取后释放写入的条件,与eWrite_2_sema模式配合)
};
public:
explicit SynShareMem(Type type,QString memKey, unsigned int size, QString semaKey, QObject *parent = nullptr);
~SynShareMem();
//约定模式下,未接收结束就写入会写入失败,所以需要等待readFinished信号
//读取模式下不允许写入,写入返回0(失败)
bool writeMem (char *data, int size); //写入共享内存-固定长度
//void writeMem(QByteArray data); //非固定长度
int readMem(char *data,int size);
//目前所有模式都可以清空
void cleanMem(char data = 0x00);
void stop();
protected:
void run();
signals:
void haveRead(char *data,int size); //共享内存读取一次就发送一次,只有读取模式下有效
//void haveRead(QByteArray data); //非固定长度
void readFinished(); //只有双锁写入模式下有效,标识对方进程已经读取共享内存数据,此时调用writeMem函数不会被阻塞
private:
bool quitFlag;
Type mType; //类型
QSharedMemory *shareMem;
QSystemSemaphore *mSema_W2R;
QSystemSemaphore *mSema_R2W; //信号量-读取标志
char *mShareData; //缓存数据段
int mShareSize; //共享内存大小
QSharedMemory *creatShareMemory(QString key, int size, bool isWrite = 1);
void relaseShareMemory(QSharedMemory *mem);
bool writeShareMemory(QSharedMemory *mem, char *data, int size);
void readShareMemory(QSharedMemory *mem, char *data, uint size);
};
#endif // SYNSHAREMEM_H
cpp文件
#include "synsharemem.h"
SynShareMem::SynShareMem(Type type,QString memKey, unsigned int size,QString readKey,QObject *parent)
: QThread(parent)
, quitFlag(0)
, mType(type)
, shareMem(nullptr)
, mSema_W2R(nullptr)
, mSema_R2W(nullptr)
, mShareData(nullptr)
, mShareSize(size)
{
//共享数据缓存空间,只有读取模式才需要
if(mType == eRead_1_sema || mType == eRead_2_sema)
mShareData = new char[mShareSize]; //不同线程堆空间可共用
//信号量
mSema_W2R = new QSystemSemaphore(QString("%1_Write->Read").arg(readKey), 1);
if(type == eWrite_2_sema || type == eRead_2_sema)
mSema_R2W = new QSystemSemaphore(QString("%1_Read->Write").arg(readKey), 1);
//共享内存,依据类型、key、大小创建共享内存块
bool isWrite = 0;
if(mType == eWrite_1_sema || mType == eWrite_2_sema)
isWrite = 1;
shareMem = creatShareMemory(memKey,size,isWrite);
if(!shareMem)
qDebug()<<"share memery creact ERROR!";
}
SynShareMem::~SynShareMem()
{
quitFlag = 1;
shareMem->detach();
if(mSema_W2R != nullptr)
delete mSema_W2R;
if(mSema_R2W != nullptr)
delete mSema_R2W;
if(mShareData != nullptr)
delete []mShareData;
}
void SynShareMem::stop()
{
//下面代码用于子线程保证退出run函数
quitFlag = 1;
if(mSema_W2R != nullptr)
mSema_W2R->release();
if(mSema_R2W != nullptr)
mSema_R2W->release();
}
bool SynShareMem::writeMem(char *data, int size)
{
writeShareMemory(shareMem,data,size);
mSema_W2R->release();
return 1;
}
int SynShareMem::readMem(char *data, int size)
{
int minSize = qMin(size,mShareSize);
readShareMemory(shareMem,data,minSize);
return minSize;
}
void SynShareMem::cleanMem(char data)
{
QByteArray textData(mShareSize,data);
char *source = textData.data();
writeMem(source,textData.size());
}
void SynShareMem::run()
{
if(quitFlag || mType == eWrite_1_sema )
return ;
if(mType == eWrite_2_sema)
{
qDebug()<<Q_FUNC_INFO<<"run to Write_2 model while(1)......";
while(1)
{
mSema_R2W->acquire(); //等待其他进程读取结束
if(quitFlag)
return ;
emit readFinished();
}
}
//线程循环只负责读取模式
qDebug()<<Q_FUNC_INFO<<"run to Read model while(1)......";
while(1)
{
//白点操作。信号量,防止没有写入的时候该线程不断循环造成软件卡顿
mSema_W2R->acquire();
if(quitFlag)
return ;
qDebug()<<Q_FUNC_INFO<<"find write and start to read...";
//内存操作
readShareMemory(shareMem,mShareData,mShareSize);
//黑点操作
if(mType == eRead_2_sema)
mSema_R2W->release();
emit haveRead(mShareData,mShareSize);
}
}
QSharedMemory* SynShareMem::creatShareMemory(QString key,int size,bool isWrite) //默认创建内存段
{
bool haveError = 0;
QSharedMemory *mem = new QSharedMemory(key,this);
if(!mem->attach()) //绑定内存块
{
//不存在该共享内存块,需要创建建(因为不确定哪一方先运行,这里不分写入与读取)
//这里需要注意,是否需要加进程间的互斥锁
mem->lock();
haveError = !mem->create(size);
mem->unlock();
}
if(haveError)
{
qDebug() << Q_FUNC_INFO<<mem->errorString();
delete mem;
mem = NULL;
}
qDebug()<<Q_FUNC_INFO<<QString("create shareMemory end! isWrite:%1 error:%2")
.arg(isWrite)
.arg(mem->errorString());
return mem;
}
void SynShareMem::relaseShareMemory(QSharedMemory* mem)
{
if(mem == NULL) return ;
mem->detach(); //所有进程全部detach后,会自动释放内存块
delete mem;
mem = NULL;
qDebug()<<Q_FUNC_INFO<<"Relase shareMemory end!";
}
bool SynShareMem::writeShareMemory(QSharedMemory* mem,char *data,int size)
{
if(mem == NULL) return 0;
mem->lock();
char *dest = reinterpret_cast<char *>(mem->data());
int minSize = qMin(size,mem->size());
memcpy(dest, data, minSize);
mem->unlock();
qDebug()<<Q_FUNC_INFO<<"Write shareMemory end!";
return 1;
}
void SynShareMem::readShareMemory(QSharedMemory* mem,char *data,uint size)
{
if(mem == NULL) return ;
mem->lock();
char *source = (char*)mem->constData();
memcpy(data, source, size);
mem->unlock();
qDebug()<<Q_FUNC_INFO<<"Read shareMemory end!";
}
查看代码托管地址:https://gitee.com/jiangtao008/shareMemory.git
1、软件双开,一个选择写入模式,一个选择读取模式,模式依据前面说的匹配的方式选择。
2、填入相同的共享内存key和size,还有信号量key(如果无法设置可以不用管)。
3、点击设置,等待成功和失败的提示框,如果提示成功则标识共享内存创建成功或关联成功。
4、最后就是读写内存操作。
1、软件A 设置约定写入模式
2、软件B设置约定读取模式
3、软件A写入数据
4、软件B自动读取到数据并显示(共享内存同步)
5、软件B为读取模式,勾选可写入,也可对共享内存做写入操作
6、如果需要修改模式、共享内存或信号量的key、size等信息,直接编辑后点击设置即可,具体实现可查看源代码。