1. 程式人生 > >Qt 多執行緒之可重入與執行緒安全

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類,它們工作在主執行緒上,把一些耗時的操作放入獨立的工作執行緒中,當工作執行緒執行完成,把結果在主執行緒所擁有的螢幕上顯示。