上一章咱们了解了有关进程的基本知识。咱们将进程理解为相互独立的正在运行的程序。因为两者是相互独立的,就存在交互的可能性,也就是咱们所说的进程间通讯(Inter-Process Communication,IPC)。不过也正所以,咱们的一些简单的交互方式,好比普通的信号槽机制等,并不适用于进程间的相互通讯。咱们说过,进程是操做系统的基本调度单元,所以,进程间交互不可避免与操做系统的实现息息相关。服务器
Qt 提供了四种进程间通讯的方式:网络
从上面的介绍中能够看到,通用的 IPC 实现大体只有共享内存和 TCP/IP 两种。后者咱们前面已经大体介绍过(应用程序级别的 QNetworkAccessManager 或者更底层的 QTcpSocket 等);本章咱们主要介绍前者。多线程
Qt 使用QSharedMemory
类操做共享内存段。咱们能够把QSharedMemory
看作一种指针,这种指针指向分配出来的一个共享内存段。而这个共享内存段是由底层的操做系统提供,能够供多个线程或进程使用。所以,QSharedMemory
能够看作是专供 Qt 程序访问这个共享内存段的指针。同时,QSharedMemory
还提供了单一线程或进程互斥访问某一内存区域的能力。当咱们建立了QSharedMemory
实例后,可使用其create()
函数请求操做系统分配一个共享内存段。若是建立成功(函数返回true
),Qt 会自动将系统分配的共享内存段链接(attach)到本进程。函数
前面咱们说过,IPC 离不开平台特性。做为 IPC 的实现之一的共享内存也遵循这一原则。有关共享内存段,各个平台的实现也有所不一样:this
QSharedMemory
不“拥有”共享内存段。当使用了共享内存段的全部线程或进程中的某一个销毁了QSharedMemory
实例,或者全部的都退出,Windows 内核会自动释放共享内存段。QSharedMemory
“拥有”共享内存段。当最后一个线程或进程同共享内存分离,而且调用了QSharedMemory
的析构函数以后,Unix 内核会将共享内存段释放。注意,这里与 Windows 不一样之处在于,若是使用了共享内存段的线程或进程没有调用QSharedMemory
的析构函数,程序将会崩溃。QSharedMemory
不该被多个线程使用。下面咱们经过一段经典的代码来演示共享内存的使用。这段代码修改自 Qt 自带示例程序(注意这里直接使用了 Qt5,Qt4 与此相似,这里再也不赘述)。程序有两个按钮,一个按钮用于加载一张图片,而后将该图片放在共享内存段;第二个按钮用于从共享内存段读取该图片并显示出来。操作系统
//!!! Qt5 class QSharedMemory; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = 0); ~MainWindow(); private: QSharedMemory *sharedMemory; };
头文件中,咱们将MainWindow
添加一个sharedMemory
属性。这就是咱们的共享内存段。接下来得实现文件中:线程
const char *KEY_SHARED_MEMORY = "Shared"; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), sharedMemory(new QSharedMemory(KEY_SHARED_MEMORY, this)) { QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout(mainWidget); setCentralWidget(mainWidget); QPushButton *saveButton = new QPushButton(tr("Save"), this); mainLayout->addWidget(saveButton); QLabel *picLabel = new QLabel(this); mainLayout->addWidget(picLabel); QPushButton *loadButton = new QPushButton(tr("Load"), this); mainLayout->addWidget(loadButton);
构造函数初始化列表中咱们将sharedMemory
成员变量进行初始化。注意咱们给出一个键(Key),前面说过,咱们能够把QSharedMemory
看作是指向系统共享内存段的指针,而这个键就能够看作指针的名字。多个线程或进程使用同一个共享内存段时,该键值必须相同。接下来是两个按钮和一个标签用于界面显示,这里再也不赘述。指针
下面来看加载图片按钮的实现:code
connect(saveButton, &QPushButton::clicked, [=]() { if (sharedMemory->isAttached()) { sharedMemory->detach(); } QString filename = QFileDialog::getOpenFileName(this); QPixmap pixmap(filename); picLabel->setPixmap(pixmap); QBuffer buffer; QDataStream out(&buffer); buffer.open(QBuffer::ReadWrite); out << pixmap; int size = buffer.size(); if (!sharedMemory->create(size)) { qDebug() << tr("Create Error: ") << sharedMemory->errorString(); } else { sharedMemory->lock(); char *to = static_cast<char *>(sharedMemory->data()); const char *from = buffer.data().constData(); memcpy(to, from, qMin(size, sharedMemory->size())); sharedMemory->unlock(); } });
点击加载按钮以后,若是sharedMemory
已经与某个线程或进程链接,则将其断开(由于咱们就要向共享内存段写入内容了)。而后使用QFileDialog
选择一张图片,利用QBuffer
将图片数据做为char *
格式。在即将写入共享内存以前,咱们须要请求系统建立一个共享内存段(QSharedMemory::create()
函数),建立成功则开始写入共享内存段。须要注意的是,在读取或写入共享内存时,都须要使用QSharedMemory::lock()
函数对共享内存段加锁。共享内存段就是一段普通内存,因此咱们使用 C 语言标准函数memcpy()
复制内存段。不要忘记以前咱们对共享内存段加锁,在最后须要将其解锁。进程
接下来是加载按钮的代码:
connect(loadButton, &QPushButton::clicked, [=]() { if (!sharedMemory->attach()) { qDebug() << tr("Attach Error: ") << sharedMemory->errorString(); } else { QBuffer buffer; QDataStream in(&buffer); QPixmap pixmap; sharedMemory->lock(); buffer.setData(static_cast<const char *>(sharedMemory->constData()), sharedMemory->size()); buffer.open(QBuffer::ReadWrite); in >> pixmap; sharedMemory->unlock(); sharedMemory->detach(); picLabel->setPixmap(pixmap); } });
若是共享内存段已经链接,仍是用QBuffer
读取二进制数据,而后生成图片。注意咱们在操做共享内存段时仍是要先加锁再解锁。最后在读取完毕后,将共享内存段断开链接。
注意,若是某个共享内存段不是由 Qt 建立的,咱们也是能够在 Qt 应用程序中使用。不过这种状况下咱们必须使用QSharedMemory::setNativeKey()
来设置共享内存段。使用原始键(native key)时,QSharedMemory::lock()
函数就会失效,咱们必须本身保护共享内存段不会在多线程或进程访问时出现问题。
IPC 使用共享内存通讯是一个很经常使用的开发方法。多个进程间得通讯要比多线程间得通讯少一些,不过在某一族的应用情形下,好比 QQ 与 QQ 音乐、QQ 影音等共享用户头像,仍是很是有用的。