qt 多进程使用共享内存 ,加速数据读写,进程间通信 共享内存
Summary:
项目中我们有时需要使用共享内存共享数据,这样,数据不用进程IO读写,加进数据加载和落地;
程序退出时,再保存到本地;速度提升数十倍;
Part1:QSharedMemory
Windows平台下进程间通信常用方式有管道、文件映射、Socket通信和共享内存等,这里详细介绍一下Qt的共享内存机制。
Qt 框架提供了多种 IPC 技术,其中 QSharedMemory 是一种高效的共享内存方式,可以实现多个进程之间快速交换数据。本文将详细讲解 QSharedMemory 的概念、用法及其主要函数的用途;
Header: | #include <QSharedMemory> |
qmake: | QT += core |
Since: | Qt 4.4 |
Inherits: | QObject |
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();
}
代码说明
-
共享内存结构:
-
共享内存分为索引区和数据区。
-
索引区存储文件的元信息(文件名、大小、偏移量)。
-
数据区存储实际的文件数据。
-
-
进程A:
-
计算共享内存的总大小(索引区 + 数据区)。
-
将文件的元信息写入索引区。
-
将文件数据写入数据区。
-
-
进程B:
-
从索引区读取文件的元信息。
-
根据元信息从数据区读取文件数据,并保存到本地。
-
-
共享内存的锁定与解锁:
-
在读写共享内存时,使用
lock()
和unlock()
确保数据一致性。
-
-
文件管理:
-
支持多个文件的读写,通过索引区管理文件的元信息。
-
运行步骤
-
编译并运行进程A,将文件写入共享内存。
-
编译并运行进程B,从共享内存读取文件并保存到本地。
Part4: 注意事项:
-
需要确保文件名和文件路径的唯一性,避免冲突。
-
在实际应用中,可能需要处理更多的错误情况(如文件不存在、共享内存不足等)。
-
共享内存的键:多个进程必须使用相同的键才能访问同一块共享内存。
-
锁定与解锁:在读写共享内存时,必须使用
lock()
和unlock()
确保数据一致性。 -
错误处理:始终检查
create()
和attach()
的返回值,并处理可能的错误。 -
共享内存的大小:确保共享内存的大小足够容纳要存储的数据。
-
通过这种方式,可以在共享内存中高效地管理多个文件,并通过索引文件实现快速访问
可能遇到的问题:
1.读写,请使用相关的方式:
indexStream.setByteOrder(QDataStream::LittleEndian);
2.