QT經過三種形式提供了對線程的支持。它們分別是,1、平臺無關的線程類,2、線程安全的事件投遞,3、跨線程的信號-槽鏈接。這使得開發輕巧的多線程Qt程序更爲容易,並能充分利用多處理器機器的優點。多線程編程也是一個有用的模式,它用於解決執行較長時間的操做而不至於用戶界面失去響應。在Qt的早期版本中,在構建庫時有不選擇線程支持的選項,從4.0開始,線程老是有效的。html
線程類
Qt 包含下面一些線程相關的類:
QThread 提供了開始一個新線程的方法
QThreadStorage 提供逐線程數據存儲
QMutex 提供相互排斥的鎖,或互斥量
QMutexLocker 是一個便利類,它能夠自動對QMutex加鎖與解鎖
QReadWriterLock 提供了一個能夠同時讀操做的鎖
QReadLocker與QWriteLocker 是便利類,它自動對QReadWriteLock加鎖與解鎖
QSemaphore 提供了一個整型信號量,是互斥量的泛化
QWaitCondition 提供了一種方法,使得線程能夠在被另外線程喚醒以前一直休眠。
建立一個線程
爲建立一個線程,子類化QThread而且重寫它的run()函數,例如:c++
以後,建立這個線程對象的實例,調用QThread::start()。因而,在run()裏出現的代碼將會在另外線程中被執行。
注意:QCoreApplication::exec()必須老是在主線程(執行main()的那個線程)中被調用,不能從一個QThread中調用。在GUI程序中,主線程也被稱爲GUI線程,由於它是惟一一個容許執行GUI相關操做的線程。另外,你必須在建立一個QThread以前建立QApplication(or QCoreApplication)對象。程序員
線程同步
QMutex, QReadWriteLock, QSemaphore, QWaitCondition 提供了線程同步的手段。使用線程的主要想法是但願它們能夠儘量併發執行,而一些關鍵點上線程之間須要中止或等待。例如,假如兩個線程試圖同時訪問同一個全局變量,結果可能不如所願。
QMutex 提供相互排斥的鎖,或互斥量。在一個時刻至多一個線程擁有mutex,假如一個線程試圖訪問已經被鎖定的mutex,那麼它將休眠,直到擁有mutex的線程對此mutex解鎖。Mutexes經常使用來保護共享數據訪問。
QReadWriterLock 與QMutex類似,除了它對 "read","write"訪問進行區別對待。它使得多個讀者能夠共時訪問數據。使用QReadWriteLock而不是QMutex,可使得多線程程序更具備併發性。編程
QSemaphore 是QMutex的通常化,它能夠保護必定數量的相同資源,與此相對,一個mutex只保護一個資源。下面例子中,使用QSemaphore來控制對環狀緩衝的訪問,此緩衝區被生產者線程和消費者線程共享。生產者不斷向緩衝寫入數據直到緩衝末端,再從頭開始。消費者從緩衝不斷讀取數據。信號量比互斥量有更好的併發性,假如咱們用互斥量來控制對緩衝的訪問,那麼生產者,消費者不能同時訪問緩衝。然而,咱們知道在同一時刻,不一樣線程訪問緩衝的不一樣部分並無什麼危害。安全
QWaitCondition 容許線程在某些狀況發生時喚醒另外的線程。一個或多個線程能夠阻塞等待一QWaitCondition ,用wakeOne()或wakeAll()設置一個條件。wakeOne()隨機喚醒一個,wakeAll()喚醒全部。多線程
下面的例子中,生產者首先必須檢查緩衝是否已滿(numUsedBytes==BufferSize),若是是,線程停下來等待bufferNotFull條件。若是不是,在緩衝中生產數據,增長numUsedBytes,激活條件 bufferNotEmpty。使用mutex來保護對numUsedBytes的訪問。另外,QWaitCondition::wait()接收一個mutex做爲參數,這個mutex應該被調用線程初始化爲鎖定狀態。在線程進入休眠狀態以前,mutex會被解鎖。而當線程被喚醒時,mutex會處於鎖定狀態,並且,從鎖定狀態到等待狀態的轉換是原子操做,這阻止了競爭條件的產生。當程序開始運行時,只有生產者能夠工做。消費者被阻塞等待bufferNotEmpty條件,一旦生產者在緩衝中放入一個字節,bufferNotEmpty條件被激發,消費者線程因而被喚醒。併發
可重入與線程安全
在Qt文檔中,術語「可重入」與「線程安全」被用來講明一個函數如何用於多線程程序。假如一個類的任何函數在此類的多個不一樣的實例上,能夠被多個線程同時調用,那麼這個類被稱爲是「可重入」的。假如不一樣的線程做用在同一個實例上仍能夠正常工做,那麼稱之爲「線程安全」的。
大多數c++類天生就是可重入的,由於它們典型地僅僅引用成員數據。任何線程能夠在類的一個實例上調用這樣的成員函數,只要沒有別的線程在同一個實例上調用這個成員函數。舉例來說,下面的Counter 類是可重入的:app
這個類不是線程安全的,由於假如多個線程都試圖修改數據成員 n,結果未定義。這是由於c++中的++和--操做符不是原子操做。實際上,它們會被擴展爲三個機器指令:
1,把變量值裝入寄存器
2,增長或減小寄存器中的值
3,把寄存器中的值寫回內存
假如線程A與B同時裝載變量的舊值,在寄存器中增值,回寫。他們寫操做重疊了,致使變量值僅增長了一次。很明顯,訪問應該串行化:A執行123步驟時不該被打斷。使這個類成爲線程安全的最簡單方法是使用QMutex來保護數據成員:函數
QMutexLocker類在構造函數中自動對mutex進行加鎖,在析構函數中進行解鎖。隨便一提的是,mutex使用了mutable關鍵字來修飾,由於咱們在value()函數中對mutex進行加鎖與解鎖操做,而value()是一個const函數。
大多數Qt類是可重入,非線程安全的。有一些類與函數是線程安全的,它們主要是線程相關的類,如QMutex,QCoreApplication::postEvent()。post
線程與QObjects
QThread 繼承自QObject,它發射信號以指示線程執行開始與結束,並且也提供了許多slots。更有趣的是,QObjects能夠用於多線程,這是由於每一個線程被容許有它本身的事件循環。
QObject 可重入性
QObject是可重入的。它的大多數非GUI子類,像QTimer,QTcpSocket,QUdpSocket,QHttp,QFtp,QProcess也是可重入的,在多個線程中同時使用這些類是可能的。須要注意的是,這些類被設計成在一個單線程中建立與使用,所以,在一個線程中建立一個對象,而在另外的線程中調用它的函數,這樣的行爲不能保證工做良好。有三種約束須要注意:
1,QObject的孩子老是應該在它父親被建立的那個線程中建立。這意味着,你毫不應該傳遞QThread對象做爲另外一個對象的父親(由於QThread對象自己會在另外一個線程中被建立)
2,事件驅動對象僅僅在單線程中使用。明確地說,這個規則適用於"定時器機制「與」網格模塊「,舉例來說,你不該該在一個線程中開始一個定時器或是鏈接一個套接字,當這個線程不是這些對象所在的線程。
3,你必須保證在線程中建立的全部對象在你刪除QThread前被刪除。這很容易作到:你能夠run()函數運行的棧上建立對象。
儘管QObject是可重入的,但GUI類,特別是QWidget與它的全部子類都是不可重入的。它們僅用於主線程。正如前面提到過的,QCoreApplication::exec()也必須從那個線程中被調用。實踐上,不會在別的線程中使用GUI類,它們工做在主線程上,把一些耗時的操做放入獨立的工做線程中,當工做線程運行完成,把結果在主線程所擁有的屏幕上顯示。
逐線程事件循環
每一個線程能夠有它的事件循環,初始線程開始它的事件循環需使用QCoreApplication::exec(),別的線程開始它的事件循環須要用QThread::exec().像QCoreApplication同樣,QThreadr提供了exit(int)函數,一個quit() slot。
線程中的事件循環,使得線程可使用那些須要事件循環的非GUI 類(如,QTimer,QTcpSocket,QProcess)。也能夠把任何線程的signals鏈接到特定線程的slots,也就是說信號-槽機制是能夠跨線程使用的。對於在QApplication以前建立的對象,QObject::thread()返回0,這意味着主線程僅爲這些對象處理投遞事件,不會爲沒有所屬線程的對象處理另外的事件。能夠用QObject::moveToThread()來改變它和它孩子們的線程親緣關係,假如對象有父親,它不能移動這種關係。在另外一個線程(而不是建立它的那個線程)中delete QObject對象是不安全的。除非你能夠保證在同一時刻對象不在處理事件。能夠用QObject::deleteLater(),它會投遞一個DeferredDelete事件,這會被對象線程的事件循環最終選取到。
假如沒有事件循環運行,事件不會分發給對象。舉例來講,假如你在一個線程中建立了一個QTimer對象,但從沒有調用過exec(),那麼QTimer就不會發射它的timeout()信號.對deleteLater()也不會工做。(這一樣適用於主線程)。你能夠手工使用線程安全的函數QCoreApplication::postEvent(),在任什麼時候候,給任何線程中的任何對象投遞一個事件,事件會在那個建立了對象的線程中經過事件循環派發。事件過濾器在全部線程中也被支持,不過它限定被監視對象與監視對象生存在同一線程中。相似地,QCoreApplication::sendEvent(不是postEvent()),僅用於在調用此函數的線程中向目標對象投遞事件。
從別的線程中訪問QObject子類
QObject和全部它的子類是非線程安全的。這包括整個的事件投遞系統。須要牢記的是,當你正從別的線程中訪問對象時,事件循環能夠向你的QObject子類投遞事件。假如你調用一個不生存在當前線程中的QObject子類的函數時,你必須用mutex來保護QObject子類的內部數據,不然會遭遇災難或非預期結果。像其它的對象同樣,QThread對象生存在建立它的那個線程中---不是當QThread::run()被調用時建立的那個線程。通常來說,在你的QThread子類中提供slots是不安全的,除非你用mutex保護了你的成員變量。
另外一方面,你能夠安全的從QThread::run()的實現中發射信號,由於信號發射是線程安全的。
跨線程的信號-槽
Qt支持三種類型的信號-槽鏈接:
1,直接鏈接,當signal發射時,slot當即調用。此slot在發射signal的那個線程中被執行(不必定是接收對象生存的那個線程)
2,隊列鏈接,當控制權回到對象屬於的那個線程的事件循環時,slot被調用。此slot在接收對象生存的那個線程中被執行
3,自動鏈接(缺省),假如信號發射與接收者在同一個線程中,其行爲如直接鏈接,不然,其行爲如隊列鏈接。
鏈接類型可能經過以向connect()傳遞參數來指定。注意的是,當發送者與接收者生存在不一樣的線程中,而事件循環正運行於接收者的線程中,使用直接鏈接是不安全的。一樣的道理,調用生存在不一樣的線程中的對象的函數也是否是安全的。QObject::connect()自己是線程安全的。
多線程與隱含共享
Qt爲它的許多值類型使用了所謂的隱含共享(implicit sharing)來優化性能。原理比較簡單,共享類包含一個指向共享數據塊的指針,這個數據塊中包含了真正原數據與一個引用計數。把深拷貝轉化爲一個淺拷貝,從而提升了性能。這種機制在幕後發生做用,程序員不須要關心它。若是深刻點看,假如對象須要對數據進行修改,而引用計數大於1,那麼它應該先detach()。以使得它修改不會對別的共享者產生影響,既然修改後的數據與原來的那份數據不一樣了,所以不可能再共享了,因而它先執行深拷貝,把數據取回來,再在這份數據上進行修改。例如:
通常認爲,隱含共享與多線程不太和諧,由於有引用計數的存在。對引用計數進行保護的方法之一是使用mutex,但它很慢,Qt早期版本沒有提供一個滿意的解決方案。從4.0開始,隱含共享類能夠安全地跨線程拷貝,如同別的值類型同樣。它們是徹底可重入的。隱含共享真的是"implicit"。它使用匯編語言實現了原子性引用計數操做,這比用mutex快多了。
假如你在多個線程中同進訪問相同對象,你也須要用mutex來串行化訪問順序,就如同其餘可重入對象那樣。總的來說,隱含共享真的給」隱含「掉了,在多線程程序中,你能夠把它們當作是通常的,非共享的,可重入的類型,這種作法是安全的。
原文地址:http://www.cnblogs.com/hicjiajia/archive/2011/02/03/1948943.html