QT的進程與線程

該文章原創於Qter開源社區(www.qter.org),作者yafeilinux,轉載請註明出處!


導語

在前面的幾節內容中講解了Qt網絡編程的一些基本內容,這一節來看一下在Qt中進程和線程的基本應用。

環境:Windows Xp + Qt 4.8.5+Qt Creator2.8.0



目錄

一、進程
二、線程



正文


一、進程
    在設計一個應用程序時,有時不希望將一個不太相關的功能集成到程序中,或者是因爲該功能與當前設計的應用程序聯繫不大,或者是因爲該功能已經可以使用現成的程序很好的實現了,這時就可以在當前的應用程序中調用外部的程序來實現該功能,這就會使用到進程。Qt應用程序可以很容易的啓動一個外部應用程序,而且Qt也提供了在多種進程間通信的方法。
    Qt的QProcess類用來啓動一個外部程序並與其進行通信。下面我們來看一下怎麼在Qt代碼中啓動一個進程。

1.首先創建QtGui應用。
工程名稱爲“myProcess”,其他選項保持默認即可。

2.然後設計界面。
在設計模式往界面上拖入一個Push Button部件,修改其顯示文本爲“啓動一個進程”。

3.修改槽。
在按鈕上點擊鼠標右鍵,轉到其clicked()信號對應的槽,更改如下:
void MainWindow::on_pushButton_clicked()
{
     myProcess.start("notepad.exe");
}

4.進入mainwindow.h文件添加代碼。
先添加頭文件包含:#include <QProcess>,然後添加私有對象定義:QProcess myProcess;

5.運行程序。
當單擊界面上的按鈕時就會彈出一個記事本程序。

這裏我們使用QProcess對象運行了Windows系統下的記事本程序(即notepad.exe程序),因爲該程序在系統目錄中,所以這裏不需要指定其路徑。大家也可以運行其他任何的程序,只需要指定其具體路徑即可。我們看到,可以使用start()來啓動一個程序,有時啓動一個程序時需要指定啓動參數,這種情況在命令行啓動程序時是很常見的,下面來看一個例子,還在前面的例子的基礎上進行更改。

1.在mainwindow.h文件中添加代碼。
添加私有槽:
private slots:
    void showResult();

2.在mainwindow.cpp文件中添加代碼。

(1)先添加頭文件包含:#include <QDebug>,然後在構造函數中添加如下代碼:
connect(&myProcess,SIGNAL(readyRead()), this, SLOT(showResult()));

(2)然後添加showResult()槽的定義:
void MainWindow::showResult()
{
    qDebug() << "showResult: " << endl
            << QString(myProcess.readAll());
}

(3)最後將前面按鈕的單擊信號對應的槽更改爲:
void MainWindow::on_pushButton_clicked()
{
    QString program = "cmd.exe";
    QStringList arguments;
    arguments << "/c dir&pause";
    myProcess.start(program, arguments);
}
    這裏在啓動Windows下的命令行提示符程序cmd.exe時爲其提供了命令作爲參數,這樣可以直接執行該命令。當命令執行完以後可以執行showResult()槽來顯示運行的結果。這裏爲了可以顯示結果中的中文字符,使用了QString()進行編碼轉換。這需要在mian()函數中添加代碼。

3. 爲了確保可以顯示輸出的中文字符,在main.cpp文件中添加代碼。
先添加頭文件包含#include <QTextCodec>,然後在main()函數第一行代碼下面,添加如下一行代碼:
QTextCodec::setCodecForCStrings(QTextCodec::codecForLocale());

4.運行程序。
按下界面上的按鈕,會在Qt Creator中的應用程序輸出欄中輸出命令的執行結果。
    對於Qt中進程進一步的使用可以參考QProcess類的幫助文檔。在Qt中還提供了多種進程間通信的方法,大家可以在Qt幫助中查看Inter-ProcessCommunication in Qt關鍵字對應的文檔。


二、線程
   
Qt提供了對線程的支持,這包括一組與平臺無關的線程類,一個線程安全的發送事件的方式,以及跨線程的信號-槽的關聯。這些使得可以很容易的開發可移植的多線程Qt應用程序,可以充分利用多處理器的機器。多線程編程也可以有效的解決在不凍結一個應用程序的用戶界面的情況下執行一個耗時的操作的問題。關於線程的內容,大家可以在Qt幫助中參考Thread Support in Qt關鍵字。

(一)啓動一個線程
    Qt中的QThread類提供了平臺無關的線程。一個QThread代表了一個在應用程序中可以獨立控制的線程,它與進程中的其他線程分享數據,但是是獨立執行的。相對於一般的程序都是從main()函數開始執行,QThread從run()函數開始執行。默認的,run()通過調用exec()來開啓事件循環。要創建一個線程,需要子類化QThread並且重新實現run()函數。
    每一個線程可以有自己的事件循環,可以通過調用exec()函數來啓動事件循環,可以通過調用exit()或者quit()來停止事件循環。在一個線程中擁有一個事件循環,可以使它能夠關聯其他線程中的信號到本線程的槽上,這使用了隊列關聯機制,就是在使用connect()函數進行信號和槽的關聯時,將Qt::ConnectionType類型的參數指定爲Qt::QueuedConnection。擁有事件循環還可以使該線程能過使用需要事件循環的類,比如QTimer和QTcpSocket類等。注意,在線程中是無法使用任何的部件類的。
    下面來看一個在圖形界面程序中啓動一個線程的例子,在界面上有兩個按鈕,一個用於開啓一個線程,一個用於關閉該線程。

1.創建項目。
    新建Qt Gui應用,名稱爲“myThread”,類名爲“Dialog”,基類選擇QDialog。

2.設計界面。
    完成項目創建後進入設計模式,向界面中放入兩個Push Button按鈕,將第一個按鈕的顯示文本更改爲“啓動線程”,將其objectName屬性更改爲startButton;將第二個按鈕的顯示文本更改爲“終止線程”,將其objectName屬性更改爲stopButton,將其enabled屬性取消選中。

3.添加自定義線程類。
    向項目中添加新的C++類,類名設置爲“MyThread”,基類設置爲“QThread”,類型信息選擇“繼承自QObject”。完成後進入mythread.h文件,先添加一個公有函數聲明:
void stop();
然後再添加一個函數聲明和一個變量的定義:
protected:
    void run();
private:
    volatile bool stopped;
    這裏stopped變量使用了volatile關鍵字,這樣可以使它在任何時候都保持最新的值,從而可以避免在多個線程中訪問它時出錯。然後進入mythread.cpp文件中,先添加頭文件#include <QDebug>,然後在構造函數中添加如下代碼:
stopped = false;
    這裏將stopped變量初始化爲false。下面添加run()函數的定義:
void MyThread::run()
{
    qreal i = 0;
    while (!stopped)
        qDebug() << QString("in MyThread: %1").arg(i++);
    stopped = false;
}
    這裏一直判斷stopped變量的值,只要它爲false,那麼就一直打印字符串。下面添加stop()函數的定義:
void MyThread::stop()
{
    stopped = true;
}
    在stop()函數中將stopped變量設置爲了true,這樣便可以結束run()函數中的循環,從而從run()函數中退出,這樣整個線程也就結束了。這裏使用了stopped變量來實現了進程的終止,並沒有使用危險的terminate()函數。

4.在Dialog類中使用自定義的線程。
先到dialog.h文件中,添加頭文件包含:
#include "mythread.h"
    然後添加私有對象的定義:
MyThread thread;
   
下面到設計模式,分別進入兩個按鈕的單擊信號對應的槽,更改如下:

// 啓動線程按鈕
void Dialog::on_startButton_clicked()
{
    thread.start();
    ui->startButton->setEnabled(false);
    ui->stopButton->setEnabled(true);
}

// 終止線程按鈕
void Dialog::on_stopButton_clicked()
{
    if (thread.isRunning()) {
        thread.stop();
        ui->startButton->setEnabled(true);
        ui->stopButton->setEnabled(false);
    }
}
    在啓動線程時調用了start()函數,然後設置了兩個按鈕的狀態。在終止線程時,先使用isRunning()來判斷線程是否在運行,如果是,則調用stop()函數來終止線程,並且更改兩個按鈕的狀態。現在運行程序,按下“啓動線程”按鈕,查看應用程序輸出欄的輸出,然後再按下“終止線程”按鈕,可以看到已經停止輸出了。
    下面我們接着來優化這個程序,通過信號和槽來將子線程中的字符串顯示到主界面上。

1.在mythread.h文件中添加信號的定義:
signals:
void stringChanged(const QString &);

2.然後到mythread.cpp文件中更改run()函數的定義:
void MyThread::run()
{
    long int i = 0;
    while (!stopped) {
       QString str = QString("in MyThread: %1").arg(i);
       emit stringChanged(str);
       msleep(1000);
       i++;
    }
    stopped = false;
}
這裏每隔1秒就發射一次信號,裏面包含了生成的字符串。

3.到dialog.h文件中添加槽聲明:
private slots:
    void changeString(const QString &);

4.打開dialog.ui,然後向主界面上拖入一個Label標籤部件。

5.到dialog.cpp文件中,在構造函數裏面添加信號和槽的關聯
// 關聯線程中的信號和本類中的槽
connect(&thread, SIGNAL(stringChanged(QString)),
this, SLOT(changeString(QString)));

6.然後添加槽的定義:
void Dialog::changeString(const QString &str)
{
    ui->label->setText(str);
}
    這裏就是將子線程發送過來的字符串顯示到主界面上。現在可以運行程序,查看效果了。


(二)線程同步
    Qt中的QMutex、QReadWriteLock、QSemaphore和QWaitCondition類提供了同步線程的方法。雖然使用線程的思想是多個線程可以儘可能的併發執行,但是總有一些時刻,一些線程必須停止來等待其他線程。例如,如果兩個線程嘗試同時訪問相同的全局變量,結果通常是不確定的。QMutex提供了一個互斥鎖(mutex);QReadWriteLock即讀-寫鎖;QSemaphore即信號量;QWaitCondition即條件變量。

(三)可重入與線程安全
在查看Qt的幫助文檔時,在很多類的開始都寫着“All functions in this class are reentrant,或者“All functions in this class are thread-safe。在Qt文檔中,術語“可重入(reentrant)和“線程安全(thread-safe)用來標記類和函數,來表明怎樣在多線程應用程序中使用它們:
  • 一個線程安全的函數可以同時被多個線程調用,即便是這些調用使用了共享數據。因爲該共享數據的所有實例都被序列化了。
  • 一個可重入的函數也可以同時被多個線程調用,但是隻能是在每個調用使用自己的數據時。

結語


最後要注意的是,使用線程是很容易出現問題的,比如無法在主線程以外的線程中使用GUI類的問題(可以簡單的通過這樣的方式來解決:將一些非常耗時的操作放在一個單獨的工作線程中來進行,等該工作線程完成後將結果返回給主線程,最後由主線程將結果顯示到屏幕上)。大家應該謹慎的使用線程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章