導(dǎo)語
在前面的幾節(jié)內(nèi)容中講解了Qt網(wǎng)絡(luò)編程的一些基本內(nèi)容,這一節(jié)來看一下在Qt中進(jìn)程和線程的基本應(yīng)用。
環(huán)境:Windows Xp + Qt 4.8.5+Qt Creator2.8.0
目錄
一、進(jìn)程 二、線程
正文
一、進(jìn)程 ? ??在設(shè)計一個應(yīng)用程序時,有時不希望將一個不太相關(guān)的功能集成到程序中,或者是因為該功能與當(dāng)前設(shè)計的應(yīng)用程序聯(lián)系不大,或者是因為該功能已經(jīng)可以使用現(xiàn)成的程序很好的實現(xiàn)了,這時就可以在當(dāng)前的應(yīng)用程序中調(diào)用外部的程序來實現(xiàn)該功能,這就會使用到進(jìn)程。Qt應(yīng)用程序可以很容易的啟動一個外部應(yīng)用程序,而且Qt也提供了在多種進(jìn)程間通信的方法。 ? ? Qt的QProcess類用來啟動一個外部程序并與其進(jìn)行通信。下面我們來看一下怎么在Qt代碼中啟動一個進(jìn)程。
1.首先創(chuàng)建QtGui應(yīng)用。 工程名稱為“myProcess”,其他選項保持默認(rèn)即可。
2.然后設(shè)計界面。 在設(shè)計模式往界面上拖入一個Push Button部件,修改其顯示文本為“啟動一個進(jìn)程”。
3.修改槽。 在按鈕上點擊鼠標(biāo)右鍵,轉(zhuǎn)到其clicked()信號對應(yīng)的槽,更改如下: void?MainWindow::on_pushButton_clicked() { ? ???myProcess.start("notepad.exe"); }
4.進(jìn)入mainwindow.h文件添加代碼。 先添加頭文件包含:#include
5.運行程序。 當(dāng)單擊界面上的按鈕時就會彈出一個記事本程序。
這里我們使用QProcess對象運行了Windows系統(tǒng)下的記事本程序(即notepad.exe程序),因為該程序在系統(tǒng)目錄中,所以這里不需要指定其路徑。大家也可以運行其他任何的程序,只需要指定其具體路徑即可。我們看到,可以使用start()來啟動一個程序,有時啟動一個程序時需要指定啟動參數(shù),這種情況在命令行啟動程序時是很常見的,下面來看一個例子,還在前面的例子的基礎(chǔ)上進(jìn)行更改。
1.在mainwindow.h文件中添加代碼。 添加私有槽: private?slots: ? ??void?showResult();
2.在mainwindow.cpp文件中添加代碼。
(1)先添加頭文件包含:#include
(2)然后添加showResult()槽的定義: void?MainWindow::showResult() { ? ??qDebug() << "showResult: " << endl ? ?? ?? ?? ?< (3)最后將前面按鈕的單擊信號對應(yīng)的槽更改為: void?MainWindow::on_pushButton_clicked() { ? ??QString?program?=?"cmd.exe"; ? ??QStringList?arguments; ? ??arguments?<"/c?dir&pause"; ? ??myProcess.start(program, arguments); } ? ??這里在啟動Windows下的命令行提示符程序cmd.exe時為其提供了命令作為參數(shù),這樣可以直接執(zhí)行該命令。當(dāng)命令執(zhí)行完以后可以執(zhí)行showResult()槽來顯示運行的結(jié)果。這里為了可以顯示結(jié)果中的中文字符,使用了QString()進(jìn)行編碼轉(zhuǎn)換。這需要在mian()函數(shù)中添加代碼。
3.?為了確保可以顯示輸出的中文字符,在main.cpp文件中添加代碼。 先添加頭文件包含#include
4.運行程序。 按下界面上的按鈕,會在Qt Creator中的應(yīng)用程序輸出欄中輸出命令的執(zhí)行結(jié)果。 ? ??對于Qt中進(jìn)程進(jìn)一步的使用可以參考QProcess類的幫助文檔。在Qt中還提供了多種進(jìn)程間通信的方法,大家可以在Qt幫助中查看Inter-ProcessCommunication in Qt關(guān)鍵字對應(yīng)的文檔。
二、線程 ? ? Qt提供了對線程的支持,這包括一組與平臺無關(guān)的線程類,一個線程安全的發(fā)送事件的方式,以及跨線程的信號-槽的關(guān)聯(lián)。這些使得可以很容易的開發(fā)可移植的多線程Qt應(yīng)用程序,可以充分利用多處理器的機(jī)器。多線程編程也可以有效的解決在不凍結(jié)一個應(yīng)用程序的用戶界面的情況下執(zhí)行一個耗時的操作的問題。關(guān)于線程的內(nèi)容,大家可以在Qt幫助中參考Thread Support in Qt關(guān)鍵字。
(一)啟動一個線程 ? ? Qt中的QThread類提供了平臺無關(guān)的線程。一個QThread代表了一個在應(yīng)用程序中可以獨立控制的線程,它與進(jìn)程中的其他線程分享數(shù)據(jù),但是是獨立執(zhí)行的。相對于一般的程序都是從main()函數(shù)開始執(zhí)行,QThread從run()函數(shù)開始執(zhí)行。默認(rèn)的,run()通過調(diào)用exec()來開啟事件循環(huán)。要創(chuàng)建一個線程,需要子類化QThread并且重新實現(xiàn)run()函數(shù)。 ? ??每一個線程可以有自己的事件循環(huán),可以通過調(diào)用exec()函數(shù)來啟動事件循環(huán),可以通過調(diào)用exit()或者quit()來停止事件循環(huán)。在一個線程中擁有一個事件循環(huán),可以使它能夠關(guān)聯(lián)其他線程中的信號到本線程的槽上,這使用了隊列關(guān)聯(lián)機(jī)制,就是在使用connect()函數(shù)進(jìn)行信號和槽的關(guān)聯(lián)時,將Qt::ConnectionType類型的參數(shù)指定為Qt::QueuedConnection。擁有事件循環(huán)還可以使該線程能過使用需要事件循環(huán)的類,比如QTimer和QTcpSocket類等。注意,在線程中是無法使用任何的部件類的。 ? ??下面來看一個在圖形界面程序中啟動一個線程的例子,在界面上有兩個按鈕,一個用于開啟一個線程,一個用于關(guān)閉該線程。
1.創(chuàng)建項目。 ? ? 新建Qt Gui應(yīng)用,名稱為“myThread”,類名為“Dialog”,基類選擇QDialog。
2.設(shè)計界面。 ? ??完成項目創(chuàng)建后進(jìn)入設(shè)計模式,向界面中放入兩個Push Button按鈕,將第一個按鈕的顯示文本更改為“啟動線程”,將其objectName屬性更改為startButton;將第二個按鈕的顯示文本更改為“終止線程”,將其objectName屬性更改為stopButton,將其enabled屬性取消選中。
3.添加自定義線程類。 ? ??向項目中添加新的C++類,類名設(shè)置為“MyThread”,基類設(shè)置為“QThread”,類型信息選擇“繼承自QObject”。完成后進(jìn)入mythread.h文件,先添加一個公有函數(shù)聲明: void stop(); 然后再添加一個函數(shù)聲明和一個變量的定義: protected: ? ? void?run(); private: ? ? volatile bool stopped; ? ??這里stopped變量使用了volatile關(guān)鍵字,這樣可以使它在任何時候都保持最新的值,從而可以避免在多個線程中訪問它時出錯。然后進(jìn)入mythread.cpp文件中,先添加頭文件#include
4.在Dialog類中使用自定義的線程。 先到dialog.h文件中,添加頭文件包含: #include "mythread.h" ? ??然后添加私有對象的定義: MyThread thread; ? ? 下面到設(shè)計模式,分別進(jìn)入兩個按鈕的單擊信號對應(yīng)的槽,更改如下:
// 啟動線程按鈕 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); ? ? } } ? ??在啟動線程時調(diào)用了start()函數(shù),然后設(shè)置了兩個按鈕的狀態(tài)。在終止線程時,先使用isRunning()來判斷線程是否在運行,如果是,則調(diào)用stop()函數(shù)來終止線程,并且更改兩個按鈕的狀態(tài)。現(xiàn)在運行程序,按下“啟動線程”按鈕,查看應(yīng)用程序輸出欄的輸出,然后再按下“終止線程”按鈕,可以看到已經(jīng)停止輸出了。 ? ??下面我們接著來優(yōu)化這個程序,通過信號和槽來將子線程中的字符串顯示到主界面上。
1.在mythread.h文件中添加信號的定義: signals: void stringChanged(const QString &);
2.然后到mythread.cpp文件中更改run()函數(shù)的定義: 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秒就發(fā)射一次信號,里面包含了生成的字符串。
3.到dialog.h文件中添加槽聲明: private?slots: ? ??void?changeString(const?QString?&);
4.打開dialog.ui,然后向主界面上拖入一個Label標(biāo)簽部件。
5.到dialog.cpp文件中,在構(gòu)造函數(shù)里面添加信號和槽的關(guān)聯(lián): //?關(guān)聯(lián)線程中的信號和本類中的槽 connect(&thread,?SIGNAL(stringChanged(QString)), this, SLOT(changeString(QString)));
6.然后添加槽的定義: void?Dialog::changeString(const?QString?&str) { ? ??ui->label->setText(str); } ? ??這里就是將子線程發(fā)送過來的字符串顯示到主界面上?,F(xiàn)在可以運行程序,查看效果了。
(二)線程同步 ? ? Qt中的QMutex、QReadWriteLock、QSemaphore和QWaitCondition類提供了同步線程的方法。雖然使用線程的思想是多個線程可以盡可能的并發(fā)執(zhí)行,但是總有一些時刻,一些線程必須停止來等待其他線程。例如,如果兩個線程嘗試同時訪問相同的全局變量,結(jié)果通常是不確定的。QMutex提供了一個互斥鎖(mutex);QReadWriteLock即讀-寫鎖;QSemaphore即信號量;QWaitCondition即條件變量。
(三)可重入與線程安全 在查看Qt的幫助文檔時,在很多類的開始都寫著“All functions in this class are reentrant”,或者“All functions in this class are thread-safe”。在Qt文檔中,術(shù)語“可重入(reentrant)”和“線程安全(thread-safe)”用來標(biāo)記類和函數(shù),來表明怎樣在多線程應(yīng)用程序中使用它們: 一個線程安全的函數(shù)可以同時被多個線程調(diào)用,即便是這些調(diào)用使用了共享數(shù)據(jù)。因為該共享數(shù)據(jù)的所有實例都被序列化了。 一個可重入的函數(shù)也可以同時被多個線程調(diào)用,但是只能是在每個調(diào)用使用自己的數(shù)據(jù)時。
結(jié)語
最后要注意的是,使用線程是很容易出現(xiàn)問題的,比如無法在主線程以外的線程中使用GUI類的問題(可以簡單的通過這樣的方式來解決:將一些非常耗時的操作放在一個單獨的工作線程中來進(jìn)行,等該工作線程完成后將結(jié)果返回給主線程,最后由主線程將結(jié)果顯示到屏幕上)。大家應(yīng)該謹(jǐn)慎的使用線程。