上一章咱們瞭解了有關進程的基本知識。咱們將進程理解爲相互獨立的正在運行的程序。因爲兩者是相互獨立的,就存在交互的可能性,也就是咱們所說的進程間通訊(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 影音等共享用戶頭像,仍是很是有用的。