|
![]() |
Qt 作為一種基于 C++ 的跨平臺 GUI 系統(tǒng),能夠提供給用戶構(gòu)造圖形用戶界面的強(qiáng)大功能。為了滿足用戶構(gòu)造復(fù)雜圖形界面系統(tǒng)的需求,Qt 提供了豐富的多線程編程支持。 Qt 作為一種基于 C++ 的跨平臺 GUI 系統(tǒng),能夠提供給用戶構(gòu)造圖形用戶界面的強(qiáng)大功能。為了滿足用戶構(gòu)造復(fù)雜圖形界面系統(tǒng)的需求,Qt 提供了豐富的多線程編程支持。從 2.2 版本開始,Qt 主要從下面三個方面對多線程編程提供支持:一、構(gòu)造了一些基本的與平臺無關(guān)的線程類;二、提交用戶自定義事件的 Thread-safe 方式;三、多種線程間同步機(jī)制,如信號量,全局鎖。這些都給用戶提供了極大的方便。不過,在某些情況下,使用定時(shí)器機(jī)制能夠比利用 Qt 本身的多線程機(jī)制更方便地實(shí)現(xiàn)所需要的功能,同時(shí)也避免了不安全的現(xiàn)象發(fā)生。本文不僅對 Qt 中的多線程支持機(jī)制進(jìn)行了討論,還著重探討了利用定時(shí)器機(jī)制模擬多線程編程的方法。
不同的平臺對 Qt 的多線程支持方式是不同的。當(dāng)用戶在 Windows 操作系統(tǒng)上安裝 Qt 系統(tǒng)時(shí),線程支持是編譯器的一個選項(xiàng),在 Qt 的 mkfiles 子目錄中包括了不同種類編譯器的編譯文件,其中帶有 -mt 后綴的文件才是支持多線程的。 而在 Unix 操作系統(tǒng)中,線程的支持是通過在運(yùn)行 configure 腳本文件時(shí)添加 -thread 選項(xiàng)加入的。安裝過程將創(chuàng)建一個獨(dú)立的庫,即 libqt-mt,因此要支持多線程編程時(shí),必須與該庫鏈接(鏈接選項(xiàng)為-lqt-mt),而不是與通常的 Qt 庫(-lqt)鏈接。 另外,無論是何種平臺,在增加線程支持時(shí)都需要定義宏 QT_THREAD_SUPPORT(即增加編譯選項(xiàng)-DQT_THREAD_SUPPORT)。在 Windows 操作系統(tǒng)中,這一點(diǎn)通常是在 qconfig.h 文件中增加一個選項(xiàng)來實(shí)現(xiàn)的。而在 Unix 系統(tǒng)中通常添加在有關(guān)的 Makefile 文件中。
在 Qt 系統(tǒng)中與線程相關(guān)的最重要的類當(dāng)然是 QThread 類,該類提供了創(chuàng)建一個新線程以及控制線程運(yùn)行的各種方法。線程是通過 QThread::run() 重載函數(shù)開始執(zhí)行的,這一點(diǎn)很象 Java 語言中的線程類。在 Qt 系統(tǒng)中,始終運(yùn)行著一個GUI 主事件線程,這個主線程從窗口系統(tǒng)中獲取事件,并將它們分發(fā)到各個組件去處理。在 QThread 類中還有一種從非主事件線程中將事件提交給一個對象的方法,也就是 QThread::postEvent()方法,該方法提供了 Qt 中的一種 Thread-safe 的事件提交過程。提交的事件被放進(jìn)一個隊(duì)列中,然后 GUI 主事件線程被喚醒并將此事件發(fā)給相應(yīng)的對象,這個過程與一般的窗口系統(tǒng)事件處理過程是一樣的。值得注意的是,當(dāng)事件處理過程被調(diào)用時(shí),是在主事件線程中被調(diào)用的,而不是在調(diào)用QThread::postEvent 方法的線程中被調(diào)用。比如用戶可以從一個線程中迫使另一個線程重畫指定區(qū)域:
然而,只有一個線程類是不夠的,為編寫出支持多線程的程序,還需要實(shí)現(xiàn)兩個不同的線程對共有數(shù)據(jù)的互斥訪問,因此 Qt 還提供了 QMutex 類,一個線程在訪問臨界數(shù)據(jù)時(shí),需要加鎖,此時(shí)其他線程是無法對該臨界數(shù)據(jù)同時(shí)加鎖的,直到前一個線程釋放該臨界數(shù)據(jù)。通過這種方式才能實(shí)現(xiàn)對臨界數(shù)據(jù)的原子操作。 除此之外,還需要一些機(jī)制使得處于等待狀態(tài)的線程在特定情況下被喚醒。QWaitCondition 類就提供了這種功能。當(dāng)發(fā)生特定事件時(shí),QWaitCondition 將喚醒等待該事件的所有線程或者喚醒任意一個被選中的線程。
在 Qt 系統(tǒng)中,定義了很多種類的事件,如定時(shí)器事件、鼠標(biāo)移動事件、鍵盤事件、窗口控件事件等。通常,事件都來自底層的窗口系統(tǒng),Qt 的主事件循環(huán)函數(shù)從系統(tǒng)的事件隊(duì)列中獲取這些事件,并將它們轉(zhuǎn)換為 QEvent,然后傳給相應(yīng)的 QObjects 對象。 除此之外,為了滿足用戶的需求,Qt 系統(tǒng)還提供了一個 QCustomEvent 類,用于用戶自定義事件,這些自定義事件可以利用 QThread::postEvent() 或者QApplication::postEvent() 被發(fā)給各種控件或其他 QObject 實(shí)例,而 QWidget 類的子類可以通過 QWidget::customEvent() 事件處理函數(shù)方便地接收到這些自定義的事件。需要注意的是:QCustomEvent 對象在創(chuàng)建時(shí)都帶有一個類型標(biāo)識 id 以定義事件類型,為了避免與 Qt 系統(tǒng)定義的事件類型沖突,該 id 值應(yīng)該大于枚舉類型 QEvent::Type 中給出的 "User" 值。 在下面的例子中,顯示了多線程編程中如何利用用戶自定義事件類。 UserEvent類是用戶自定義的事件類,其事件標(biāo)識為346798,顯然不會與系統(tǒng)定義的事件類型沖突。
UserThread類是由QThread類繼承而來的子類,在該類中除了定義有關(guān)的變量和線程控制函數(shù)外,最主要的是定義線程的啟動函數(shù)UserThread::run(),在該函數(shù)中創(chuàng)建了一個用戶自定義事件UserEvent,并利用QThread類的postEvent函數(shù)提交該事件給相應(yīng)的接收對象。
UserWidget類是用戶定義的用于接收自定義事件的QWidget類的子類,該類利用slotGo()函數(shù)創(chuàng)建了一個新的線程recv(UserThread類),當(dāng)收到相應(yīng)的自定義事件(即id為346798)時(shí),利用customEvent函數(shù)對事件進(jìn)行處理。
在這個例子中,UserWidget對象中創(chuàng)建了新的線程UserThread,用戶可以利用這個線程實(shí)現(xiàn)一些周期性的處理(如接收底層發(fā)來的消息等),一旦滿足特定條件就提交一個用戶自定義的事件,當(dāng)UserWidget對象收到該事件時(shí),可以按需求做出相應(yīng)的處理,而一般情況下,UserWidget對象可以正常地執(zhí)行某些例行處理,而完全不受底層消息的影響。
4、利用定時(shí)器機(jī)制實(shí)現(xiàn)多線程編程
為了避免Qt系統(tǒng)中多線程編程帶來的問題,還可以使用系統(tǒng)中提供的定時(shí)器機(jī)制來實(shí)現(xiàn)類似的功能。定時(shí)器機(jī)制將并發(fā)的事件串行化,簡化了對并發(fā)事件的處理,從而避免了thread-safe方面問題的出現(xiàn)。 在下面的例子中,同時(shí)有若干個對象需要接收底層發(fā)來的消息(可以通過Socket、FIFO等進(jìn)程間通信機(jī)制),而消息是隨機(jī)收到的,需要有一個GUI主線程專門負(fù)責(zé)接收消息。當(dāng)收到消息時(shí)主線程初始化相應(yīng)對象使之開始處理,同時(shí)返回,這樣主線程就可以始終更新界面顯示并接收外界發(fā)來的消息,達(dá)到同時(shí)對多個對象的控制;另一方面,各個對象在處理完消息后需要通知GUI主線程。對于這個問題,可以利用第3節(jié)中的用戶自定義事件的方法,在主線程中安裝一個事件過濾器,來捕捉從各個對象中發(fā)來的自定義事件,然后發(fā)出信號調(diào)用主線程中的一個槽函數(shù)。 另外,也可以利用Qt中的定時(shí)器機(jī)制實(shí)現(xiàn)類似的功能,而又不必?fù)?dān)心Thread-safe問題。下面就是有關(guān)的代碼部分: 在用戶定義的Server類中創(chuàng)建和啟動了定時(shí)器,并利用connect函數(shù)將定時(shí)器超時(shí)與讀取設(shè)備文件數(shù)據(jù)相關(guān)聯(lián):
slotReadFile函數(shù)負(fù)責(zé)在定時(shí)器超時(shí)時(shí),從文件中讀取數(shù)據(jù),然后重新啟動定時(shí)器:
在該程序中,利用了類似輪循的方式定時(shí)對用戶指定的設(shè)備文件進(jìn)行讀取,根據(jù)讀到的數(shù)據(jù)內(nèi)容將信息發(fā)送到各個相應(yīng)的對象。用戶可以在自己的GUI主線程中創(chuàng)建一個Server類,幫助實(shí)現(xiàn)底層的消息接收過程,而本身仍然可以處理諸如界面顯示的問題。當(dāng)各個對象完成處理后,通過重新啟動定時(shí)器繼續(xù)進(jìn)行周期性讀取底層設(shè)備文件的過程。當(dāng)然,這種方法適合于各對象對事件的處理時(shí)間較短,而底層設(shè)備發(fā)來消息的頻率又相對較慢的情況。在這種情況下,上述方法完全可以滿足用戶的需求,而又避免了處理一些與線程并發(fā)有關(guān)的復(fù)雜問題。 當(dāng)然,利用定時(shí)器機(jī)制實(shí)現(xiàn)多線程編程在某些方面具有一定的局限性,有關(guān)到底如何實(shí)現(xiàn)多線程編程,如何編寫出效率更高的代碼,還有待于開發(fā)者進(jìn)一步研究和探討。 (1)Qt官方文檔 http://doc.trolltech.com/3.2/index.html (2)Qt源代碼:QThread, QCustomEvent,QTimer. (3)Advanced Programming in the UNIX Environment, W. Richard Stevens. |
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
