共享内存同步封装

1 说明

软件开发中,经常需要使用到进程通信,数据量大了共享内存便是比较好德选择。但是共享内存一般需要信号量配合实现共享内存同步。

再qt中,共享内存被封装为QSharedMemory类,而信号量被封装为QSystemSemaphore类,底层实现看源代码都是调用标准库接口。

而本文就是在QSharedMemory与QSystemSemaphore的基础上继续进行一次封装,从而实现共享内存及其同步的功能。

gitee项目代码托管地址:https://gitee.com/jiangtao008/shareMemory.git
共享内存文章:https://blog.csdn.net/weixin_42887343/article/details/118416994

2 读写模式

共享内存一般存在读取与写入端,我对读取与写入做了一下分类,读写各分成两中模式,如下:

  • 直接写入模式
  • 约定写入模式
  • 条件读取模式
  • 约定读取模式

本小结将对其进行分析。

2.1 符号说明

信号量自旋等待
当一个进程试图去获取一个已经锁定的信号量时,该线程不会像自旋锁一样在自旋忙等待,而是会将自身加入一个等待队列中去睡眠,直到其他进程或线程释放信号量后,处于等待队列中的进程才会被唤醒。
在这里插入图片描述
信号量释放
释放信号量,用于唤醒其他进程或线程。
在这里插入图片描述

2.2 直接写入模式

直接写入就是直接写入…即向共享内存中写入数据的时候直接写即可,不需要考虑到有进程在读取共享内存(有共享内存锁,无信号量)。
共享内存写入后,需要释放信号量,让其他进程读取。
共享内存同步封装_第1张图片

2.3 约定写入模式

约定写入即需要向读取写入双方都需要约定好。在没有写入端写入前,读取端不能读取,反过来,在读取前也不能写入。

所以写入内存前需要信号量自旋等待,等待读取进程读取内存后释放信号量释。
写入内存后同样需要释放另外一个信号量(和上面的信号量自旋等待不是一个信号量)。
共享内存同步封装_第2张图片

2.4 条件读取模式

最基本的就是条件读取模式,需要等待写入信号量释放后再读取。
如果直接读取,一个是进程也不知道内存是否被写入,不断重复读取回浪费cpu资源,第二就是定时读取不能达到内存同步的实时性(写入内存后,可能需要等待n毫秒后数据才被读取),所以最好的方法就是等待信号量释放后立马读取。
共享内存同步封装_第3张图片

2.5 约定读取模式

约定读取是用于配合约定写入而设计的模式,写入端在使用约定写入模式下,读取端读取内存后必须要释放对应的信号量,否则会导致写入端写入数据时一直等待。
共享内存同步封装_第4张图片

读写模式匹配

无条件写入 + 条件读取

共享内存同步封装_第5张图片

无条件写入 + 约定读取

共享内存同步封装_第6张图片

约定写入 + 约定读取

共享内存同步封装_第7张图片

共享内存同步类实现

思路

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!";
}

类的使用方法—示范代码

界面设计

共享内存同步封装_第8张图片

软件实现代码

查看代码托管地址:https://gitee.com/jiangtao008/shareMemory.git

使用方法

1、软件双开,一个选择写入模式,一个选择读取模式,模式依据前面说的匹配的方式选择。
2、填入相同的共享内存key和size,还有信号量key(如果无法设置可以不用管)。
3、点击设置,等待成功和失败的提示框,如果提示成功则标识共享内存创建成功或关联成功。
4、最后就是读写内存操作。

使用示例

1、软件A 设置约定写入模式
共享内存同步封装_第9张图片
2、软件B设置约定读取模式
共享内存同步封装_第10张图片
3、软件A写入数据
共享内存同步封装_第11张图片
4、软件B自动读取到数据并显示(共享内存同步)
共享内存同步封装_第12张图片
5、软件B为读取模式,勾选可写入,也可对共享内存做写入操作
共享内存同步封装_第13张图片
6、如果需要修改模式、共享内存或信号量的key、size等信息,直接编辑后点击设置即可,具体实现可查看源代码。

你可能感兴趣的:(C++\QT,共享内存,信号量,共享内存同步,qt,进程通信)