qt 多进程使用共享内存 ,加速数据读写,进程间通信 共享内存

embedded/2025/3/13 16:07:14/

Summary:

项目中我们有时需要使用共享内存共享数据,这样,数据不用进程IO读写,加进数据加载和落地;

程序退出时,再保存到本地;速度提升数十倍;

Part1:QSharedMemory

Windows平台下进程间通信常用方式有管道、文件映射、Socket通信和共享内存等,这里详细介绍一下Qt的共享内存机制。

Qt 框架提供了多种 IPC 技术,其中 QSharedMemory 是一种高效的共享内存方式,可以实现多个进程之间快速交换数据。本文将详细讲解 QSharedMemory 的概念、用法及其主要函数的用途;

Header:

#include <QSharedMemory>

qmake:

QT += core

Since:

Qt 4.4

Inherits:

QObject

  • List of all members, including inherited members
  • Obsolete members

1.QSharedMemory 的核心特点

    唯一键(Key)标识:
        每块共享内存通过唯一的键(字符串)标识。
        不同进程通过相同的键连接到共享内存。

    线程安全性:
        提供锁机制(lock() 和 unlock())以保护共享内存的读写。

    跨平台支持:
        Qt 的跨平台特性使 QSharedMemory 可以在不同操作系统上无缝使用。

2. QSharedMemory 的工作流程

共享内存的基本使用可以分为以下几个步骤:

    创建共享内存:
        第一个进程通过 create(size) 创建一块共享内存。
        分配的大小由数据的存储需求决定。

    附加到共享内存:
        其他进程通过 attach() 方法连接到已有的共享内存。

    数据读写:
        通过 lock() 和 unlock() 保证线程安全,获取内存指针后读写数据。

    释放共享内存:
        调用 detach() 断开与共享内存的连接。

3.QSharedMemory 常用函数

1. 构造函数与析构函数
QSharedMemory(const QString &key, QObject *parent = nullptr)创建一个 QSharedMemory 对象,并指定共享内存的键(key)。key 是共享内存的唯一标识符,多个进程通过相同的 key 访问同一块共享内存。~QSharedMemory()析构函数,释放共享内存资源。2. 共享内存的创建与销毁
bool create(int size, QSharedMemory::AccessMode mode = ReadWrite)创建大小为 size 的共享内存段。mode 指定共享内存的访问模式:QSharedMemory::ReadOnly:只读模式。QSharedMemory::ReadWrite:读写模式(默认)。如果共享内存已存在,则返回 false。bool attach(QSharedMemory::AccessMode mode = ReadWrite)附加到已存在的共享内存段。mode 指定访问模式(同上)。如果附加成功,返回 true。bool detach()从共享内存段分离。分离后,进程不再访问共享内存,但共享内存段仍然存在。void setKey(const QString &key)设置共享内存的键(key)。QString key() const返回共享内存的键。void setNativeKey(const QString &key)设置平台原生的共享内存键(适用于需要直接使用系统共享内存键的场景)。QString nativeKey() const返回平台原生的共享内存键。3. 共享内存的访问
void *data()返回指向共享内存数据的指针。如果共享内存未附加或未创建,返回 nullptr。const void *constData() const返回指向共享内存数据的常量指针。适用于只读访问。int size() const返回共享内存段的大小(以字节为单位)。4. 共享内存的锁定与解锁
bool lock()锁定共享内存,确保当前进程独占访问。如果锁定成功,返回 true。bool unlock()解锁共享内存,允许其他进程访问。如果解锁成功,返回 true。5. 错误处理
QSharedMemory::SharedMemoryError error() const返回最后一次发生的错误类型。错误类型包括:QSharedMemory::NoError:无错误。QSharedMemory::PermissionDenied:权限不足。QSharedMemory::InvalidSize:无效的大小。QSharedMemory::KeyError:键错误。QSharedMemory::AlreadyExists:共享内存已存在。QSharedMemory::NotFound:共享内存未找到。QSharedMemory::LockError:锁定失败。QSharedMemory::OutOfResources:系统资源不足。QSharedMemory::UnknownError:未知错误。QString errorString() const返回最后一次错误的描述信息。6. 共享内存的状态
bool isAttached() const检查当前进程是否已附加到共享内存段。如果已附加,返回 true。

Part3: 读写文件样例:

写样例:

QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
QDataStream out(&buffer);
out.setByteOrder(QDataStream::LittleEndian);// 假设 metaInfo 是已填充的结构体
out.writeRawData((char*)&metaInfo, sizeof(FileMetaInfo));// 将 buffer 数据写入共享内存
sharedMemory.lock();
memcpy(sharedMemory.data(), buffer.data().constData(), buffer.size());
sharedMemory.unlock();

读样例:

    QBuffer buffer;qDebug() << "sharedMemory.size():" << sharedMemory.size();buffer.setData((char*)sharedMemory.constData(), sharedMemory.size());buffer.open(QIODevice::ReadOnly);buffer.seek(0);qDebug() << "Buffer size:" << buffer.size();qDebug() << "First bytes:" << QByteArray::fromRawData((char*)sharedMemory.constData(), 16).toHex();QDataStream inStream(&buffer);inStream.device()->seek(0);  // 确保 QDataStream 位置正确inStream.setByteOrder(QDataStream::LittleEndian);FileMetaInfo metaPtr ;inStream.readRawData((char*)&metaPtr, sizeof(FileMetaInfo));qDebug() << "File:" << metaPtr.fileName<< "Size:" << metaPtr.fileSize<< "Offset:" << metaPtr.dataOffset;
读写多个文件样例:
共享内存结构定义
#include <QSharedMemory>
#include <QBuffer>
#include <QDataStream>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QVector>// 定义文件元信息结构
#pragma pack(1)
struct FileMetaInfo {char fileName[256];    // 文件名qint64 fileSize;     // 文件大小qint64 dataOffset;   // 文件数据在共享内存中的偏移量FileMetaInfo() : fileSize(0), dataOffset(0) {memset(fileName, 0, sizeof(fileName)); // 初始化 fileName 为空字符串}
};
#pragma pack()
// 定义共享内存的索引区大小(假设最多存储 10 个文件)
const int MAX_FILES = 10;
const int INDEX_SIZE = MAX_FILES * sizeof(FileMetaInfo);
进程A:写入文件到共享内存
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 创建共享内存对象QSharedMemory sharedMemory("MySharedMemory");// 假设要写入的文件列表QStringList filePaths = { "file1.txt", "file2.txt", "file3.txt" };// 计算共享内存总大小(索引区 + 数据区)qint64 totalDataSize = 0;for (const QString &filePath : filePaths) {QFile file(filePath);if (file.open(QIODevice::ReadOnly)) {totalDataSize += file.size();file.close();}}qint64 totalSharedMemorySize = INDEX_SIZE + totalDataSize;// 创建共享内存if (!sharedMemory.create(totalSharedMemorySize)) {qDebug() << "Failed to create shared memory:" << sharedMemory.errorString();return 1;}// 写入索引区和数据区sharedMemory.lock();// 写入索引区QBuffer indexBuffer;indexBuffer.open(QBuffer::ReadWrite);QDataStream indexStream(&indexBuffer);qint64 currentDataOffset = INDEX_SIZE; // 数据区从索引区之后开始for (const QString &filePath : filePaths) {QFile file(filePath);if (file.open(QIODevice::ReadOnly)) {FileMetaInfo metaInfo;metaInfo.fileName = QFileInfo(file).fileName();metaInfo.fileSize = file.size();metaInfo.dataOffset = currentDataOffset;// 写入索引信息indexStream << metaInfo.fileName << metaInfo.fileSize << metaInfo.dataOffset;// 写入文件数据到共享内存char *to = (char*)sharedMemory.data() + currentDataOffset;file.read(to, metaInfo.fileSize);currentDataOffset += metaInfo.fileSize;file.close();}}// 将索引区数据写入共享内存char *indexTo = (char*)sharedMemory.data();const char *indexFrom = indexBuffer.data().data();memcpy(indexTo, indexFrom, indexBuffer.size());sharedMemory.unlock();qDebug() << "Files written to shared memory.";return a.exec();
}
进程B:从共享内存读取文件
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 创建共享内存对象QSharedMemory sharedMemory("MySharedMemory");// 附加到共享内存if (!sharedMemory.attach()) {qDebug() << "Failed to attach to shared memory:" << sharedMemory.errorString();return 1;}// 读取索引区sharedMemory.lock();QBuffer indexBuffer;indexBuffer.setData((char*)sharedMemory.constData(), INDEX_SIZE);indexBuffer.open(QBuffer::ReadOnly);QDataStream indexStream(&indexBuffer);QVector<FileMetaInfo> fileMetaInfos;while (!indexStream.atEnd()) {FileMetaInfo metaInfo;indexStream >> metaInfo.fileName >> metaInfo.fileSize >> metaInfo.dataOffset;fileMetaInfos.append(metaInfo);}// 读取文件数据for (const FileMetaInfo &metaInfo : fileMetaInfos) {QByteArray fileData(metaInfo.fileSize, 0);const char *from = (char*)sharedMemory.constData() + metaInfo.dataOffset;memcpy(fileData.data(), from, metaInfo.fileSize);// 将文件数据保存到本地QFile file(metaInfo.fileName);if (file.open(QIODevice::WriteOnly)) {file.write(fileData);file.close();qDebug() << "File saved:" << metaInfo.fileName;}}sharedMemory.unlock();// 分离共享内存sharedMemory.detach();return a.exec();
}

代码说明

  1. 共享内存结构

    • 共享内存分为索引区和数据区。

    • 索引区存储文件的元信息(文件名、大小、偏移量)。

    • 数据区存储实际的文件数据。

  2. 进程A

    • 计算共享内存的总大小(索引区 + 数据区)。

    • 将文件的元信息写入索引区。

    • 将文件数据写入数据区。

  3. 进程B

    • 从索引区读取文件的元信息。

    • 根据元信息从数据区读取文件数据,并保存到本地。

  4. 共享内存的锁定与解锁

    • 在读写共享内存时,使用lock()unlock()确保数据一致性。

  5. 文件管理

    • 支持多个文件的读写,通过索引区管理文件的元信息。


运行步骤

  1. 编译并运行进程A,将文件写入共享内存。

  2. 编译并运行进程B,从共享内存读取文件并保存到本地。

Part4: 注意事项:

  • 需要确保文件名和文件路径的唯一性,避免冲突。

  • 在实际应用中,可能需要处理更多的错误情况(如文件不存在、共享内存不足等)。

  • 共享内存的键:多个进程必须使用相同的键才能访问同一块共享内存。

  • 锁定与解锁:在读写共享内存时,必须使用 lock()unlock() 确保数据一致性。

  • 错误处理:始终检查 create()attach() 的返回值,并处理可能的错误。

  • 共享内存的大小:确保共享内存的大小足够容纳要存储的数据。

  • 通过这种方式,可以在共享内存中高效地管理多个文件,并通过索引文件实现快速访问

可能遇到的问题:

1.读写,请使用相关的方式:

 indexStream.setByteOrder(QDataStream::LittleEndian);

2.


http://www.ppmy.cn/embedded/172291.html

相关文章

纯前端全文检索的两种实现方案:ElasticLunr.js 和 libsearch

纯前端全文检索的两种实现方案&#xff1a;ElasticLunr.js 和 libsearch 在前端开发中&#xff0c;实现全文检索功能可以显著提升用户体验&#xff0c;尤其是在处理大量文本数据时。本文将介绍两种流行的纯前端全文检索方案&#xff1a;ElasticLunr.js 和 libsearch。这两种方…

never_give_up

一个很有意思的题&#xff1a; never_give_up - Bugku CTF平台 注意到注释里面有1p.html&#xff0c;我们直接在源代码界面看&#xff0c;这样就不会跳转到它那个链接的&#xff1a; 然后解码可得&#xff1a; ";if(!$_GET[id]) {header(Location: hello.php?id1);exi…

Pandas数据清洗实战之清洗猫眼电影

本次案例所需要用到的模块 pandas(文件读取保存 操作表格的模块) 将上次Scrapy爬取下来的文件 做个数据清洗 变成我们想要的数据 确定目的&#xff1a;将此文件中的duration字段中的分钟 和publisher_time上映去掉 只保留纯数值 数据清洗题目如下: 修复 publish_time列中的错…

基于Tkinter与DeepSeek API的多模型智能聊天系统设计与实现

基于Tkinter与DeepSeek API的多模型智能聊天系统设计与实现 一、系统概述 本文介绍一款基于Python Tkinter GUI框架和DeepSeek API开发的智能聊天系统。该系统具备多模型切换、主题定制、对话管理三大核心功能&#xff0c;采用模块化设计实现高效的异步通信机制。程序支持Win…

数势科技黎科峰:DeepSeek 引爆数据分析“奇点”,创造普惠化新机遇丨数据猿专访...

大数据产业创新服务媒体 ——聚焦数据 改变商业 2025年国内人工智能“春天”的脚步竟然来得如此急促。当人们还沉浸在春节假期之中&#xff0c;一场波及多个行业的巨变悄然发生&#xff0c;1月20日&#xff0c;DeepSeek R1横空出世&#xff0c;打破了先前多数投资人保守的“判…

本地部署资源聚合搜索神器 Jackett 并实现外部访问

Jackett 是一款免费的开源跨平台的资源聚合搜索软件。它支持众多种子站和网盘站&#xff0c;可以作为代理服务器通过 API 来响应来自应用程序&#xff0c;然后将结果发送回请求软件‌。而且支持跨平台。 本文将详细的介绍如何利用 Docker 在本地部署 Jackett 并结合路由侠实现…

国产FPGA往事

本原创文章由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处&#xff08;www.meyesemi.com) 我去年和紫光同创原厂的技术专家写了一本书——《国产FPGA权威开发指南》&#xff0c;我想请FPGA开发者同行多多指点和…

无需 Docker 也能下载镜像!轻松获取 Docker 镜像文件!

背景问题 在日常开发或运维工作中&#xff0c;我们经常需要下载 Docker 镜像&#xff0c;但可能会遇到以下问题&#xff1a; &#x1f539; 服务器无法访问 Docker Hub&#xff0c;导致 docker pull 失败。 &#x1f539; Windows 端没有安装 Docker&#xff0c;但仍然需要获…