Qt 多線程之可重入與線程安全 上篇
Qt 多線程之可重入與線程安全是本節要介紹的內容。在Qt文檔中,術語“可重入”與“線程安全”被用來說明一個函數如何用于多線程程序。假如一個類的任何函數在此類的多個不同的實例上,可以被多個線程同時調用,那么這個類被稱為是“可重入”的。假如不同的線程作用在同一個實例上仍可以正常工作,那么稱之為“線程安全”的。
大多數c++類天生就是可重入的,因為它們典型地僅僅引用成員數據。任何線程可以在類的一個實例上調用這樣的成員函數,只要沒有別的線程在同一個實例上調用這個成員函數。舉例來講,下面的Counter 類是可重入的:
class Counter
{
public:
Counter() {n=0;}
void increment() {++n;}
void decrement() {--n;}
int value() const {return n;}
private:
int n;
};
這個類不是線程安全的,因為假如多個線程都試圖修改數據成員 n,結果未定義。這是因為c++中的++和--操作符不是原子操作。實際上,它們會被擴展為三個機器指令:
1,把變量值裝入寄存器
2,增加或減少寄存器中的值
3,把寄存器中的值寫回內存
假如線程A與B同時裝載變量的舊值,在寄存器中增值,回寫。他們寫操作重疊了,導致變量值僅增加了一次。很明顯,訪問應該串行化:A執行123步驟時不應被打斷。使這個類成為線程安全的最簡單方法是使用QMutex來保護數據成員:
class Counter
{
public:
Counter() { n = 0; }
void increment() { QMutexLocker locker(&mutex); ++n; }
void decrement() { QMutexLocker locker(&mutex); --n; }
int value() const { QMutexLocker locker(&mutex); return n; }
private:
mutable QMutex mutex;
int n;
};
QMutexLocker類在構造函數中自動對mutex進行加鎖,在析構函數中進行解鎖。隨便一提的是,mutex使用了mutable關鍵字來修飾,因為我們在value()函數中對mutex進行加鎖與解鎖操作,而value()是一個const函數。
大多數Qt類是可重入,非線程安全的。有一些類與函數是線程安全的,它們主要是線程相關的類,如QMutex,QCoreApplication::postEvent()。
線程與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類,它們工作在主線程上,把一些耗時的操作放入獨立的工作線程中,當工作線程運行完成,把結果在主線程所擁有的屏幕上顯示。
小結:Qt 多線程之可重入與線程安全關于本節的內容介紹完了,請接著看 Qt 多線程之逐線程事件循環 下篇。更多資料請參考編輯推薦。