歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Qt線程基礎

線程基礎

保謂線程?

線程與並行處理任務息息相關,就像進程一樣。那麼,線程與進程有什麼區別呢?當你在電子表格上進行數據結算的時候,在相同的桌面上可能有一個播放器正在播放你最喜歡的歌曲。這是一個兩個進程並行工作的例子:一個進程運行電子表格程序;另一個進程運行一個媒體播放器。這種情況最適合用多任務這個詞來描述。進一步觀察媒體播放器,你會發現在這個進程內,又存在並行的工作。當媒體播放器向音頻驅動發送音樂數據的時候,用戶界面上與之相關的信息不斷地進行更新。這就是單個進程內的並行線程。

    那麼,線程的並行性是如何實現的呢?在單核CPU計算機上,並行工作類似在電影院中不停移動圖像產生的一種假象。對於進程而言,在很短的時間內中斷占有處理器的進程就形成了這種假象。然而,處理器遷移到下一個進程。為了在不同進程之間進行切換,當前程序計算器被保存,下一個程序計算器被加載進來。這還不夠,相關寄存器以及一些體系結構和操作系統特定的數據也要進行保存和重新加載。

    就像一個CPU可以支撐兩個或多個進程一樣,同樣也可以讓CPU在單個進程內運行不同的代碼片段。當一個進程啟動時,它問題執行一個代碼片斷從而該進程就被認為是擁有了一個線程。但是,該程序可以會決定啟動第二個線程。這樣,在一個進程內部,兩個不同的代碼序列就需要被同步處理。通過不停地保存當前線程的程序計數器和相關寄存器,同時加載下一個線程的程序計數器和相關寄存器,就可以在單核CPU上實現並行。在不同活躍線程之間的切換不需要這些線程之間的任何協作。當切換到下一個線程時,當前線程可能處於任一種狀態。

    當前CPU設計的趨勢是擁有多個核。一個典型的單線程應用程序只能利用一個核。但是,一個多線程程序可被分配給多個核,便得程序以一種完全並行的方式運行。這樣,將一個任務分配給多個線程使得程序在多核CPU計算機上的運行速度比傳統的單核CPU計算機上的運行速度快很多。

GUI 線程和工作者線程

    如上所述,每個程序啟動後就會擁有一個線程。該線程稱為”主線程”(在Qt應用程序中也叫”GUI線程”)。Qt GUI必須運行在此線程上。所有的圖形元件和幾個相關的類,如QPixmap,不能工作於非主線程中。非主線程通常稱為”工作者線程”,因為它主要處理從主線程中卸下的一些工作。

數據的同步訪問

    每個線程都有自己的棧,這意味著每個線程都擁有自己的調用歷史和本地變量。不同於進程,同一進程下的線程之間共享相同的地址空間。下圖顯示了內存中的線程塊圖。非活躍線程的程序計數器和相關寄存器通常保存在內核空間中。對每個線程來說,存在一個共享的代碼片段和一個單獨的棧。


如果兩個線程擁有一個指向相同對象的指針,那麼兩個線程可以同時去訪問該對象,這可以破壞該對象的完整性。很容易想象的情形是一個對象的兩個方法同時執行可能會出錯。

    有時,從不同線程中訪問一個對象是不可避免的。例如,當位於不同線程中的許多對象之間需要進行通信時。由於線程之間使用相同的地址空間,線程之間進行數據交換要比進程之間進行數據交換快得多。數據不需要序列化然後拷貝。線程之間傳遞指針是允許的,但是必須嚴格協調哪些線程使用哪些指針。禁止在同一對象上執行同步操作。有一些方法可以實現這種要求,下面描述其中的一些方法。

    那麼,怎樣做才安全呢?在一個線程中創建的所有對象在線程內部使用是安全的,前提條件是其他線程沒有引用該線程中創建的一些對象且這些對象與其他的線程之間沒有隱性耦合關系。當數據作為靜態成員變量,單例或全局數據方式共享時,這種隱性耦合是可能發生的。

使用線程

基本上,對線程來講,有兩種使用情形:

·        利用多核處理器使處理速度更快。

·        將一些處理時間較長或阻塞的任務移交給其他的線程,從而保證GUI線程或其他對時間敏感的線程保持良好的反應速度。

何時不應使用線程

    開發者在使用線程時必須特意小心。啟動其他線程很容易,但很難保證所有共享的數據仍然是一致的。這些問題通常很難找到,因為它們可以在某個時候僅顯示一次或僅在某種硬件配置下出現。在創建線程解決某些問題之前,如下的一些方法也應該考慮一下。


非線程方式

說明

QEventLoop::processEvents()

在一個耗時的計算中不停地調用QEventLoop::processEvents()能以免GUI被阻塞。但是,這種解決方式並不能用於更大范圍的計算操作中,因為會導致調用 processEvents()太頻繁或不夠,取決於硬件。.

QTimer

有時,在後台進程中使用一個計時器來調度在將來某個時間點運行一段程序非常方便。超時時間為0的計時器將在事件處理完後立即觸發。

QSocketNotifierQNetworkAccessManagerQIODevice::readyRead()

當在一個低速的網絡連接上進行阻塞讀的時候,可以不使用多線程。只要對一塊網絡數據的計算可以很快地執行,那麼,這種交互式的設計比線程中的同步等待要好些。交互式設計比多線程要不容易出錯且更有效。在許多情況下,也有一些性能上的提升。


一般來講,建議只使用安全的且已被驗證過的路徑,避免引入線程概念。 QtConcurrent提供了一種簡易的接口,來將工作分配到所有的處理器的核上。線程相關代碼已經完全隱藏在QtConcurrent 框架中,因此,開發者不需要關注這些細節。但是, QtConcurrent 不能用於那麼需要與運行中的線程進行通信的情形,且它也不能用於處理阻塞操作。

該使用哪種 Qt 線程技術?

有時,我們不僅僅只是在另一個線程中運行一個方法。可能需要位於其他線程中的某個對象為GUI線程提供服務。也許,你想其他的線程一直保持活躍狀態去不停地輪詢硬件端口並在一個需要關注的事件發生時發送一個信號給GUI線程。Qt提供了不同的解決方案來開發多線程應用程序。正確的解決方案取決於新線程的目的以及它的生命周期。


線程的生命周期

開發任務

解決方案

單次調用

在其他的線程中運行一個方法,當方法運行結束後退出線程。

Qt 提供了不同的解決方案:

·         1.編寫一個函數,然後利用 QtConcurrent::run()運行它。

·         2.從QRunnable 派生一個類,並利用全局線程池QThreadPool::globalInstance()->start()來運行它。

·         3. 從QThread派生一個類, 重載QThread::run() 方法並使用QThread::start()來運行它。

單次調用

在容器中的所有項執行相同的一些操作。執行過程中使用所有可用的核。一個通用的例子就是從一個圖像列表中產生縮略圖。

QtConcurrent 提供了 map()函數來將這些操作應用於於容器中的每個項中,filter() 用於選擇容器元素,以及指定一個刪減函數的選項來與容器中剩下的元素進行合並。

單次調用

一個耗時的操作必須放到另一個線程中運行。在這期間,狀態信息必須發送到GUI線程中。

使用 QThread,,重載run方法並根據情況發送信號。.使用queued信號/槽連接來連接信號與GUI線程的槽。

常駐

有一對象位於另一個線程中,將讓其根據不同的請求執行不同的操作。這意味與工作者線程之間的通信是必須的。

QObject 派生一個類並實現必要的槽和信號,將對象移到一個具有事件循環的線程中,並通過queued信號/槽連接與對象進行通信。

常駐

對象位於另一個線程中,對象不斷執行重復的任務如輪詢某個端口,並與GUI線程進行通��。

與上述類似,但同時在工作者線程中使用一個計時器來實現輪詢。但是,最好的解決方案是完全避免輪詢。有時,使用 QSocketNotifier 是一種不錯的選擇。


Copyright © Linux教程網 All Rights Reserved