当前位置: 首页 > news >正文

67.QT-QSharedMemory

1.QSharedMemory介绍

QSharedMemory提供了多个线程和进程对共享内存段的访问。它还提供了一种方法,让单个线程或进程锁定内存以进行独占访问。

当使用这个类时,请注意以下平台差异:

  • Windows: QSharedMemory不“拥有”共享内存段。当有QSharedMemory实例附加到特定共享内存段的所有线程或进程销毁了它们的QSharedMemory实例或者退出了,Windows内核会自动释放共享内存段。
  • Unix: QSharedMemory“拥有”共享内存段。当最后一个线程或进程将一个QSharedMemory实例附加到一个特定的共享内存段时,通过销毁它的QSharedMemory实例从这个段中分离出来,Unix内核释放这个共享内存段。但是如果最后一个线程或进程在没有运行QSharedMemory析构函数的情况下崩溃了(未释放),共享内存段会在崩溃时幸存下来。

在对共享内存进行读写操作之前,记得使用lock()锁定共享内存,并且记得在操作完成后使用unlock()释放锁。

它的构造函数如下所示:

QSharedMemory(const QString &key, QObject *parent = nullptr)

通过指定的parent和key,构造一个共享内存对象。由于key已经被设置,所以不需要再调用setKey()或setNativeKey()指定要访问的内存块了,create()和attach()就可以被调用了。

其它常用函数如下所示:

QString QSharedMemory::key() const //返回被setKey()所赋值的key如果未设置key则返回空字符串,如果应用程序是和非Qt平台的,则需要使用nativeKey()访问 bool QSharedMemory::lock() //用来锁定共享内存的互斥值,锁住成功则返回true. 若另一个进程已经锁住了共享内存段,本函数将会阻塞直到锁被另一个进程释放。到那时,本函数才会获得锁并返回true. 如果本函数返回false,那就说明你已经忽略了一个由create()或attach()返回的false,而其原因可能是由于某个系统错误而导致setNativeKey()或QSystemSemaphore::acquire()失败。 bool QSharedMemory::unlock() //释放共享内存段上的锁并返回true,如果共享内存段没有被lock,或者如果锁被其他进程持有,本函数什么都不会做而只是返回false. bool QSharedMemory::create(int size, AccessMode mode=ReadWrite) //创建一个大小为size个字节的共享内存段, 然后用给定的访问模式mode附着到共享内存上。Mode取值有以下几种: QSharedMemory::ReadOnly : 共享内存段是只读的。不允许写入共享内存段。尝试写入使用ReadOnly创建的共享内存段会导致程序中止。 QSharedMemory::ReadWrite : 允许对共享内存段进行读写操作。 bool QSharedMemory::attach(AccessMode mode = ReadWrite) //尝试将进程附加到由传递给构造函数或调用setKey()或setNativeKey()的键标识的共享内存段上。默认访问模式为“ReadWrite”。也可以是ReadOnly。如果附加操作成功,则返回true。如果返回false,则调用error()或者errorString()来确定发生了哪个错误。在附加共享内存段成功之后,则可以通过调用data()来获得一个指向共享内存的指针。 bool QSharedMemory::isAttached() const //如果该进程附加到共享内存段,则返回true。 bool QSharedMemory::detach() //将进程从共享内存段中分离。如果这是连接到共享内存段的最后一个进程,那么共享内存段将被系统释放,也就是说,内容将被销毁。如果函数分离了共享内存段,则返回true。如果它返回false,通常意味着该段没有连接,或者被另一个进程锁定。 void * QSharedMemory::data() //如果附加了共享内存段,则返回指向共享内存段内容的指针。否则返回null。在对共享内存进行读写操作之前,记得使用lock()锁定共享内存,并且记得在操作完成后使用unlock()释放锁。

2.QSharedMemory示例

界面如下所示(左边写入字符串、右边实时更新):

写内存widget-示例代码如下所示:

#include "widget.h" #include "ui_widget.h" #include <QDebug> #include <QTextEdit> #include <QByteArray> Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget), sharememory("shareTest", this) { ui->setupUi(this); } Widget::~Widget() { delete ui; } void Widget::on_textEdit_textChanged() { if(sharememory.isAttached()) { sharememory.detach(); } QByteArray arr = ui->textEdit->toPlainText().toLocal8Bit(); int len = arr.length(); qDebug()<<ui->textEdit->toPlainText()<<len; if(!sharememory.create(len)) { qDebug() << sharememory.errorString(); return ; } sharememory.lock(); char *dest = reinterpret_cast<char *>(sharememory.data()); for (int i = 0; i < len; i++) { dest[i] = arr[i]; } sharememory.unlock(); }

读内存widget-示例代码如下所示:

#include "widget.h" #include "ui_widget.h" #include <QDebug> #include <QTextEdit> #include <QTimer> #include <QByteArray> Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget), sharememory("shareTest", this) { ui->setupUi(this); QTimer *timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); timer->start(200); } void Widget::timeout() { if (!sharememory.isAttached()) { if (!sharememory.attach()) { qDebug() << sharememory.errorString(); } else { sharememory.lock(); QByteArray arr((char*)sharememory.data(), sharememory.size()); ui->textEdit->setPlainText(QString(arr)); sharememory.unlock(); sharememory.detach(); } } } Widget::~Widget() { delete ui; }

3.Linux下查看共享内存

1、查看共享内存,使用命令:ipcs -m

2、删除共享内存,使用命令:ipcrm -m [shmid]

如下图所示:

1、key:共享内存的key

2、shmid:共享内存的编号

3、owner:共享内存的创建用户

4、perms:共享内存的权限

5、bytes:共享内存的大小

6、nattch:连接到共享内存的进程数

7、status:共享内存的状态

4.QSharedMemory与其他语言进程通信

如果使用QSharedMemory与非QT程序通信,就没法使用setKey()来设置共享内存名称了,需要使用setNativeKey()设置名称和nativeKey()访问。

注意:使用setNativeKey()后,QSharedMemory的lock()和unlock()函数也会失效。

接下来我们来实现一个主进程Master示例来模拟非Qt的程序:

并且需要和子进程维护协议如下所示:

4.1 主进程发送区

发送区用于主进程下发协议用的,当主进程填入一个完整的json文本给子进程后,子进程接收到就会去处理json文本,并把发送区buff[0]=’\0’进行清空处理。
如果发送区填入的是一个非完整的json文本,比如”12345”, 子进程不会做任何清空处理。

4.2 主进程接收区

接收区用于主进程接收数据用的,主进程需要主动去读取该接收区内容,当主进程通过发送区发送一段json文本后,如果json文本有返回内容,那么就要开启定时器去轮询读取子进程返回的数据内容。如果读取到内容,主进程可以通过buff[0]=’\0’进行清空处理

4.3 心跳区

用于检测子进程是否启动成功,并检测子进程是否正常运行
当主进程启动子进程并且参数格式正确后,子进程会立即更新心跳区内容为一个正整数unsigned int数字,然后会定时1S去进行数字加1(从0递增),并更新内容中的数字。
提示:

  • 主进程可以启动子进程后,去定时轮询心跳区内容,如果更新为一个数字说明启动子进程成功,如果超过5秒都没内容,说明启动进程失败,即可弹窗通知报错等操作。
  • 当检测到启动成功后,可以定时去读取心跳区内容是否每隔2秒进行更新,如果长时间未更新说明子进程挂掉或通信失败、需要杀掉所有同名子进程,然后重新启动一个新的子进程来进行交互处理。

4.4 主进程核心代码

主进程头文件成员如下所示:

QList<QSharedMemory*> _memList; QSharedMemory _memTx; QSharedMemory _memRx; QSharedMemory _memHeartbeat; unsigned int _heart{999};

主进程示例cpp代码如下所示:

#include "widget.h" #include "ui_widget.h" #include <QDebug> #include <QMessageBox> #include <QTimer> #include <QProcess> #include <QJsonDocument> Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) , _memTx(this) , _memRx(this) , _memHeartbeat(this) { ui->setupUi(this); _memTx.setNativeKey("_memTxTest"); _memRx.setNativeKey("_memRxTest"); _memHeartbeat.setNativeKey("_memHeartbeatTest"); qDebug()<<"_memTx:"<<_memTx.nativeKey(); _memList = {&_memTx, &_memRx, &_memHeartbeat}; int size = 4096; for(int i = 0; i < _memList.length();i++) { if(_memList[i]->isAttached()) { // 如果已存在,则先尝试分离,再重新创建,这是一种更健壮的做法 _memList[i]->detach(); } if (!_memList[i]->create(size)) { qDebug() << "创建共享内存失败:" << _memList[i]->errorString(); QTimer::singleShot(2000, this, [&]{ QMessageBox::warning(NULL,"创建共享内存失败", "创建共享内存失败",QMessageBox::Ok); QApplication::instance()->quit(); }); return; } qDebug() << "共享内存创建成功,大小:" << _memList[i]->size(); } QTimer *timer = new QTimer(this); connect(timer, &QTimer::timeout, this, [&]{ if (!_memRx.isAttached()) { if (!_memRx.attach()) { qDebug() << "attach _memRx.errorString:" << _memRx.errorString(); } } if(_memRx.isAttached()) { _memRx.lock(); QByteArray arr((char*)_memRx.data()); if(arr.length()) { // 处理主进程接收区域内容,处理完后 dest[0] = '\0' 清空处理 qDebug()<<"_memRx arr"<<arr.length(); ui->_textRecv->setPlainText(QString(arr)); QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(arr, &error); if(error.error == QJsonParseError::NoError) { char *dest = reinterpret_cast<char *>(_memRx.data()); dest[0] = '\0'; } qDebug()<<"recv _memRx:"<<arr.length()<<arr; } _memRx.unlock(); } if (!_memHeartbeat.isAttached()) { if (!_memHeartbeat.attach()) { qDebug() << "attach _memHeartbeat.errorString:" << _memHeartbeat.errorString(); } } if(_memHeartbeat.isAttached()) { _memHeartbeat.lock(); QByteArray memHeartbeat((char*)_memHeartbeat.data()); if(memHeartbeat.length()) { // 处理心跳值 uint data = QString(memHeartbeat).toUInt(); if(data != _heart) { ui->_textHeartbeat->setPlainText(QString(memHeartbeat)); qDebug()<<"recv _memHeartbeat:"<<memHeartbeat.length()<<memHeartbeat; _heart = data; } } _memHeartbeat.unlock(); } }); timer->start(200); } Widget::~Widget() { delete ui; } void Widget::on_pushButton_2_clicked() // 打开子进程 按钮 { QProcess* m_curProcess = new QProcess(this); m_curProcess->start(QCoreApplication::applicationDirPath() + "/" + ui->lineExeName->text(), { _memTx.nativeKey() ,_memRx.nativeKey() ,_memHeartbeat.nativeKey() }); } void Widget::on_pushButton_clicked() // 发送 按钮(下发通信) { QByteArray arr = ui->_textSend->toPlainText().toUtf8(); int len = arr.length(); if(!len) return; _memTx.lock(); char *dest = reinterpret_cast<char *>(_memTx.data()); strcpy(dest, arr); _memTx.unlock(); } void Widget::on_pushButton_3_clicked() // 读回发送区域按钮,读取发送区域,判断子进程是否处理完成 { char *dest = reinterpret_cast<char *>(_memTx.data()); qDebug()<<"读回发送区域:"<<dest; ui->_textSend->setPlainText(dest); _memTx.unlock(); }

4.5 子进程核心代码

头文件相关成员和函数如下所示:

void iniMemorys(QString masterSend, QString masterRecv, QString heartbeat); void sendMaster(QVariantMap map); void handlerMasterData(QVariantMap map); QSharedMemory _masterTx; QSharedMemory _masterRx; QSharedMemory _memHeartbeat; unsigned int _memHeartbeatCnt; // 心跳值

cpp相关函数实现如下所示:

void ShareMemoryComm::iniMemorys(QString masterSend, QString masterRecv, QString heartbeat) { _masterTx.setNativeKey(masterSend); _masterRx.setNativeKey(masterRecv); _memHeartbeat.setNativeKey(heartbeat); _memHeartbeatCnt = 0; QTimer *heartbeatTimer = new QTimer(this); connect(heartbeatTimer, &QTimer::timeout, this, &ShareMemoryComm::heartbeatTimeout); heartbeatTimeout(); // 立即发一次心跳,告诉主进程启动成功并通信正常 heartbeatTimer->setInterval(1000); heartbeatTimer->start(); QTimer *masterTxTimer = new QTimer(this); connect(masterTxTimer, &QTimer::timeout, this, [&]{ if (!_masterTx.isAttached()) { if (!_masterTx.attach()) { qDebug() << "attach _masterTx.errorString:" << _masterTx.errorString(); return; } } _masterTx.lock(); QByteArray arr((char*)_masterTx.data()); if(arr.length()) { qDebug()<<"_masterTx 接收内容:"<<arr.length()<<arr.left(40); QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(arr, &error); if(error.error == QJsonParseError::NoError) { handlerMasterData(doc.toVariant().toMap()); char *dest = reinterpret_cast<char *>(_masterTx.data()); dest[0] = '\0'; } } _masterTx.unlock(); }); masterTxTimer->setInterval(200); masterTxTimer->start(); } void ShareMemoryComm::heartbeatTimeout() { if (!_memHeartbeat.isAttached()) { if (!_memHeartbeat.attach()) { qDebug() << "attach _memHeartbeat.errorString:" << _memHeartbeat.errorString(); return; } } _memHeartbeat.lock(); QByteArray arr = QString::number(_memHeartbeatCnt++).toUtf8(); char *dest = reinterpret_cast<char *>(_memHeartbeat.data()); strcpy(dest, arr); qDebug()<<"_memHeartbeatCnt:"<<_memHeartbeatCnt; _memHeartbeat.unlock(); } void ShareMemoryComm::handlerMasterData(QVariantMap map) { if(!map.contains("type")) return; QString type = map["type"].toString(); if(type == "messageBox") { QString title = map["title"].toString(); QString text = map["text"].toString(); map["success"] = true; map["message"] = ""; sendMaster(map); Backend::instance()->messageBoxHintShow(title, text); } } void ShareMemoryComm::sendMaster(QVariantMap map) { QByteArray arr = QJsonDocument::fromVariant(map).toJson(QJsonDocument::Compact); if (!_masterRx.isAttached()) { if (!_masterRx.attach()) { qDebug() << "attach _masterRx.errorString:" << _masterRx.errorString(); return; } } _masterRx.lock(); if(_masterRx.size() > arr.size()) { char *dest = reinterpret_cast<char *>(_masterRx.data()); strcpy(dest, arr); } _masterRx.unlock(); }
http://www.cnnetsun.cn/news/4083125.html

相关文章:

  • 加密音乐打不开?3个问题带你彻底搞懂
  • Java设计模式---代理模式
  • 第 4 篇:「当数据库坏了」— 优雅降级与七层防守
  • 《C++深度解构02》类和对象(上)——定义规范、内存对齐与 this 指针全解
  • AI智能体开发指南:企业如何构建智能体系统
  • nxdumptool 终极指南:3 步完成 Switch 卡带与数字游戏的完整备份
  • protal:基于比对的超快速宏基因组物种与菌株分辨分析谱工具
  • League Akari 完整上手指南:本地化英雄联盟辅助工具从安装到精通
  • 装完DistroAV却找不到NDI Source?NDI Runtime缺失的4类场景与彻底排查方案
  • SumatraPDF 颜色反转:5 个让你少走弯路的实用技巧
  • DDrawCompat 使用避坑指南:老游戏打不开,先别急着卸载
  • 三步搞定 DDrawCompat:让 Windows 11 重新跑起二十年前老游戏的完整指南
  • 从 GPT-5.5 迁到 Luna,我的 Agent 工作流哪里断了
  • Git Worktree 实战指南:多分支并行开发与高效工作流设计
  • 2026黄冈危房鉴定检测怎么选?老旧房危房鉴定靠谱机构 TOP 结构安全检测+ 报告可查 电话汇总
  • 突破LLM上下文限制:构建智能体原生记忆系统的分层架构与工程实践
  • arm 解决git 下载代码
  • TNGA架构与双叉臂悬架:深度解析C-HR高速行驶品质的机械奥秘
  • 魔兽争霸3卡顿变形加载失败?这套插件一次治好老游戏的现代病
  • VBA Workbook对象操作全解析:从创建、保存到关闭的自动化实践
  • AI Agent Skill开发实战:从概念到实现,打造智能体核心能力
  • 【重庆邮电大学、重庆蚂蚁消费金融有限公司主办 | 重庆举办】第一届粒球计算国际会议(ICGBC 2026)
  • Linux写论文最头疼的文献管理,被这个WPS-Zotero插件3步搞定了
  • 从单体应用到插件化架构:可组合运行时如何重塑软件开发
  • 从谍照解读新车:广汽传祺GS8 390T动力升级与市场策略分析
  • Godot游戏开发:模块化设计与信号通信实战指南
  • 在Windows Server 2012关闭Internet Explorer增强的安全配置
  • 技术资源分发与社群运营的工程化实践:从加群到自动化体系
  • ORB-SLAM3 optimizer.initializeOptimization(0);
  • 结构型模式-代理模式