Qt多線程(有詳細例子)


Qt線程類

 

Qt 包含下面一些線程相關的類:
QThread 提供了開始一個新線程的方法
QThreadStorage 提供逐線程數據存儲
QMutex  提供相互排斥的鎖,或互斥量
QMutexLocker 是一個便利類,它可以自動對QMutex加鎖與解鎖
QReadWriterLock 提供了一個可以同時讀操作的鎖
QReadLocker與QWriteLocker 是便利類,它自動對QReadWriteLock加鎖與解鎖
QSemaphore 提供了一個整型信號量,是互斥量的泛化
QWaitCondition 提供了一種方法,使得線程可以在被另外線程喚醒之前一直休眠。
 

 

Qt線程的創建

 

   Qt線程中有一個公共的抽象類,所有的線程都是從這個QThread抽象類中派生的,要實現QThread中的純虛函數run(),run()函數是通過start()函數來實現調用的。 

 

復制代碼
 1 class MyThread : public QThread {
 2     public:
 3         virtual void run();
 4     };
 5 
 6     void MyThread::run()
 7     {
 8         for( int count = 0; count < 20; count++ ) {
 9             sleep( 1 );
10             qDebug( "Ping!" );
11         }
12     }
13 
14     int main()
15     {
16         MyThread a;
17         MyThread b;
18 
19         a.start();//自動調用run(),否則即使該線程創建,也是一開始就掛起
20         b.start();
21         //要等待線程a,b都退出
22         a.wait();
23         b.wait();
24     }
25 
復制代碼

 

 

 

 

 

 

 

 

Qt線程同步

 

 

 

1.QMutex

 

 

QMutex ( bool recursive = FALSE )

 

 

virtual ~QMutex ()

 

 

void lock ()  //試圖鎖定互斥量。如果另一個線程已經鎖定這個互斥量,那么這次調用將阻塞直到那個線程把它解鎖。

 

 

void unlock ()

 

 

bool locked ()

 

 

bool tryLock () //如果另一個進程已經鎖定了這個互斥量,這個函數返回假,而不是一直等到這個鎖可用為止,比如,它不是阻塞的。

 

復制代碼
 1 //Qt
 2   QMutex mutex;
 3   void someMethod()
 4   {
 5      mutex.lock();
 6      qDebug("Hello");
 7      qDebug("World");
 8      mutex.unlock();
 9   }
10   
11 //用Java的術語,這段代碼應該是:
12   void someMethod()
13   {
14      synchronized {
15        qDebug("Hello");
16        qDebug("World");
17      }

18   } 

復制代碼

 

 

 

 

 

     不過在Qt中我們可用通過另一個類來簡化這種應用,因為如果使用QMutex.lock()而沒有對應的使用QMutex.unlcok()的話

 就會造成死鎖,別的線程永遠也得不到接觸該mutex鎖住的共享資源的機會。盡管可以不使用lock()而使用tryLock(timeout)

來避免因為死等而造成的死鎖( tryLock(負值)==lock()),但是還是很有可能造成錯誤。

   對於上述的情況MFC中用CSingleLock 或 MultiLock,Boost中用boost::mutex::scoped_lock來進行解決,而在Qt中用

QMutexLocker來進行解決。下面是沒有采用 QMutexLocker的例子和采用QMutexLocker的方案。

 

 2.QMutexLocker

 

 this complex function locks a QMutex upon entering the function and unlocks the mutex at all the exit points

復制代碼
 1 int complexFunction(int flag)
 2  {
 3      mutex.lock();
 4 
 5      int retVal = 0;
 6 
 7      switch (flag) {
 8      case 0:
 9      case 1:
10          mutex.unlock();
11          return moreComplexFunction(flag);
12      case 2:
13          {
14              int status = anotherFunction();
15              if (status < 0) {
16                  mutex.unlock();
17                  return -2;
18              }
19              retVal = status + flag;
20          }
21          break;
22      default:
23          if (flag > 10) {
24              mutex.unlock();
25              return -1;
26          }
27          break;
28      }
29 
30      mutex.unlock();
31      return retVal;
32  } 
復制代碼

 This example increases the likelihood that errors will occur.Using QMutexLocker greatly simplifies the code, and makes it more readable:

 

復制代碼
 1 int complexFunction(int flag)
 2  {
 3      QMutexLocker locker(&mutex);
 4 
 5      int retVal = 0;
 6 
 7      switch (flag) {
 8      case 0:
 9      case 1:
10          return moreComplexFunction(flag);
11      case 2:
12          {
13              int status = anotherFunction();
14              if (status < 0)
15                  return -2;
16              retVal = status + flag;
17          }
18          break;
19      default:
20          if (flag > 10)
21              return -1;
22          break;
23      }
24 
25      return retVal;
26  } 
復制代碼

 

 

 

 Now, the mutex will always be unlocked when the QMutexLocker object is destroyed (when the function returns since locker is an auto variable).即使在拋出異常的情況下也可以使用。

 

3. QReadWriteLock

 

  用mutex進行線程同步有一個問題就是mutex只允許某個時刻只允許一個線程對共享資源進行訪問,如果同時有多個線程對共享

資源進行讀訪問,而只有一個寫操作線程,那么在這種情況下如果采用mutex就成為程序運行性能的瓶頸了。在這種情況下Qt下采用

QReadWriteLock來實現多個線程讀,一個線程寫。寫線程執行的時候會阻塞所有的讀線程,而讀線程之間的運行不需要進行同步。

復制代碼
 1 MyData data;
 2 QReadWriteLock lock;
 3 void ReaderThread::run()
 4 {
 5     
 6     lock.lockForRead();
 7     access_data_without_modifying_it(&data);
 8     lock.unlock();
 9     
10 }
11 void WriterThread::run()
12 {
13     
14     lock.lockForWrite();
15     modify_data(&data);
16     lock.unlock();
17     
18 }
19 
20 
復制代碼

 

    QReadWriterLock 與QMutex相似,除了它對 "read","write"訪問進行區別對待。它使得多個讀者可以共時訪問數據。使用QReadWriteLock而不是QMutex,可以使得多線程程序更具有並發性。

 

4.QReadLocker和QWriteLocker 

 

    對於QMutex有QMutexLocker來簡化使用,而對於QReadWriteLock有 QReadLocker和QWriteLocker。

    Here's an example that uses QReadLocker to lock and unlock a read-write lock for reading:  

復制代碼
QReadWriteLock lock;

 QByteArray readData()
 {
      );
     
     return data;
 } 
復制代碼

 It is equivalent to the following code:

復制代碼
 QReadWriteLock lock;

 QByteArray readData()
 {
      lock.lockForRead();
     
      lock.unlock();
     return data;
 } 
復制代碼

 

5.QSemaphore 

 

    QSemaphore 是QMutex的一般化,它可以保護一定數量的相同資源,與此相對,一個mutex只保護一個資源。下面例子中,使用QSemaphore來控制對環狀緩沖區的訪問,此緩沖區被生產者線程和消費者線程共享。生產者不斷向緩沖寫入數據直到緩沖末端,消費者從緩沖不斷從緩沖頭部讀取數據。

    信號量比互斥量有更好的並發性,假如我們用互斥量來控制對緩沖的訪問,那么生產者,消費者不能同時訪問緩沖。然而,我們知道在同一時刻,不同線程訪問緩沖的不同部分並沒有什么危害。

      QSemaphore semaphore(1); |     QMutex mutex;

      Qsemaphore.acquire();         |     Qmutex.lock();

 

      Qsemaphore.release();         |     Qmutex.unlock();

 

 

Public Functions

 

 Semaphores support two fundamental operations, acquire() and release():

  • acquire(n) tries to acquire n resources. If there aren't that many resources available, the call will block until this is the case.
  • release(n) releases n resources.
  • tryAcquire() returns immediately if it cannot acquire the resources
  • available() returns the number of available resources at any time.

 

 Example:

復制代碼
 QSemaphore sem(5);      // sem.available() == 5

 sem.acquire(3);         // sem.available() == 2
 sem.acquire(2);         // sem.available() == 0
 sem.release(5);         // sem.available() == 5
 sem.release(5);         // sem.available() == 10

 sem.tryAcquire(1);      // sem.available() == 9, returns true
 sem.tryAcquire(250);    // sem.available() == 9, returns false 
復制代碼

 

 

     生產者線程寫數據到buffer直到緩沖末端,然后重新從buffer的頭部開始寫。

     顯然producer線程和consumer線程是需要進行同步的,If the producer generates the data too fast, it will overwrite data that the consumer hasn't yet read; if the consumer reads the data too fast, it will pass the producer and read garbage.

     A crude way to solve this problem is to have the producer fill the buffer, then wait until the consumer has read the entire buffer, and so on. 顯然這樣做效率是比較低的。

      

復制代碼
 1  const int DataSize = 100000;
 2  const int BufferSize = 8192;
 3  char buffer[BufferSize];
 4 

 5  //When the application starts, the reader thread will start  

    //acquiring "free" bytes and convert them into "used" bytes

 

 6  QSemaphore freeBytes(BufferSize);   //producer線程在此區域寫入數據,初始資源數量為BufferSize
 7  QSemaphore usedBytes;                  //consumer線程讀取此區域的數據,初始資源數量為0
 8 
 9 
10 //For this example, each byte counts as one resource.

11 //In a real-world application, we would probably operate on larger

    //units (for example, 64 or 256 bytes at a time) 

 

 

12  class Producer : public QThread
13  {
14  public:
15      void run();
16  };

17 //生產者每acquire一次就,使用掉Buffer個資源中的一個,而寫入的字符存入到buffer數組中

 

    //從而消費者可用讀取字符,從而消費者獲取一個資源 

18  void Producer::run()
19  {
20      //qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
21      for (int i = 0; i < DataSize; ++i) {
22           freeBytes.acquire();
23          buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
24           usedBytes.release();
25      }
26  }
27 
28  class Consumer : public QThread
29  {
30  public:
31      void run();
32  };
33 
34  void Consumer::run()
35  {
36      for (int i = 0; i < DataSize; ++i) {
37           usedBytes.acquire();
38          fprintf(stderr, "%c", buffer[i % BufferSize]);
39           freeBytes.release();
40      }
41      fprintf(stderr, "\n");
42  }

43 //Finally, in main(), we start the producer and consumer threads. 

    //What happens then is that the producer converts some "free" space

    //into "used" space, and the consumer can then convert it back to //"free" space.

 

46  int main(int argc, char *argv[])
47  {

48      QCoreApplication app(argc, argv);

 

 

49      Producer producer;

50      Consumer consumer;

 

 

51      producer.start();

52      consumer.start();

 

 

53      producer.wait();

54      consumer.wait();

 

 

55      return 0;

56  }

 

復制代碼

 

    producer的run函數:

   當producer線程執行run函數,如果buffer中已經滿了,而沒有consumer線程沒有讀,這樣producer就不能再往buffer

中寫字符。此時在 freeBytes.acquire處就阻塞直到consumer線程讀(consume)數據。一旦producer獲取到一個字節(資源)

就寫如一個隨機的字符,並調用 usedBytes.release從而consumer線程獲取一個資源可以讀一個字節的數據了。

    consumer的run函數:

    當consumer線程執行run函數,如果buffer中沒有數據,就是資源=0,則consumer線程在此處阻塞。直到producer線程執行

寫操作,寫入一個字節,並執行usedBytes.release從而使得consumer線程的可用資源數=1。則consumer線程從阻塞狀態中退出,

並將 usedBytes資源數-1,當前資源數=0。

 

6.QWaitCondition

 

  • QWaitCondition ()
  • virtual ~QWaitCondition ()
  • bool wait ( QMutex * mutex, unsigned long time = ULONG_MAX )
  • void wakeOne ()
  • void wakeAll ()

    Public function:

     

    bool QWaitCondition::wait ( QMutex * mutex, unsigned long time = ULONG_MAX )

     1) 釋放鎖定的mutex

     2) 在線程對象上等待

    mutex必須由調用線程進行初鎖定 。注意調用wait的話,會自動調用unlock解鎖之前鎖住的資源,不然會造成死鎖。

    線程1等待線程2來改變共享資源,從而達到一定的條件然后發出信號,使得線程1從wait中的阻塞狀態中被喚醒。

    但是線程2想改變資源,卻無法辦到,因為線程1調用lock之后就在wait中blocking,了但是沒有及時的unlock,那么這就

    構成了死鎖的條件。所以說wait函數除了使調用線程切換到內核態之外,還自動unlock(&mutex) 

     

    mutex將被解鎖,並且調用線程將會阻塞,直到下列條件之一滿足時才醒來:

    • 另一個線程使用wakeOne()或wakeAll()傳輸信號給它。在這種情況下,這個函數將返回真。
    • time毫秒過去了。如果time為ULONG_MAX(默認值),那么這個等待將永遠不會超時(這個事件必須被傳輸)。如果等待的事件超時,這個函數將會返回假互斥量將以同樣的鎖定狀態返回。這個函數提供的是允許從鎖定狀態到等待狀態的原子轉換。 

     

     void QWaitCondition::wakeAll ()

    這將會喚醒所有等待QWaitCondition的線程。這些線程被喚醒的順序依賴於操組系統的調度策略,並且不能被控制或預知。

     

    void  QWaitCondition::wakeOne ()

    這將會喚醒所有等待QWaitCondition的線程中的一個線程。這個被喚醒的線程依賴於操組系統的調度策略,並且不能被控制或預知。

     

     

     

     

 

 

 假定每次用戶按下一個鍵,我們有三個任務要同時執行,每個任務都可以放到一個線程中,每個線程的run()都應該是這樣:

復制代碼
QWaitCondition key_pressed;

  for (;;) {
     key_pressed.wait(); // 這是一個QWaitCondition全局變量
     // 鍵被按下,做一些有趣的事
     do_something();
  }
復制代碼

 或是這樣:

forever {
     mutex.lock();
     keyPressed.wait(&mutex);
     do_something();
     mutex.unlock();
 } 

 

第四個線程回去讀鍵按下並且每當它接收到一個的時候喚醒其它三個線程,就像這樣:

復制代碼
 QWaitCondition key_pressed;

  for (;;) {
     getchar();
     // 在key_pressed中導致引起任何一個線程。wait()將會從這個方法中返回並繼續執行
     key_pressed.wakeAll();
  }
復制代碼

 

    注意這三個線程被喚醒的順序是未定義的,並且當鍵被按下時,這些線程中的一個或多個還在do_something(),它們將不會被喚醒(因為它們現在沒有等待條件變量)並且這個任務也就不會針對這次按鍵執行操作。這種情況是可以避免得,比如,就像下面這樣做:

 

復制代碼
 1  QMutex mymutex;
 2   QWaitCondition key_pressed;
 3   int mycount=0;
 4 
 5   //Worker線程代碼
 6   for (;;) {

 7      key_pressed.wait(); // 這是一個QWaitCondition全局變量

 

         //keyPressed.wait(&mutex);

 8      mymutex.lock();
 9       mycount ++;

10      mymutex.unlock();

 

 

11      do_something();

 

 

12      mymutex.lock();
13       mycount --;
14      mymutex.unlock();
15   }
16 
17   // 讀取按鍵線程代碼
18   for (;;) {
19      getchar();
20      mymutex.lock();
21      // 睡眠,直到沒有忙碌的工作線程才醒來。count==0說明沒有Worker線程在do something
22      while( count > 0 ) {
23        mymutex.unlock();
24        sleep( 1 );
25        mymutex.lock();
26      }
27      mymutex.unlock();
28       key_pressed.wake All();
29   }
30 
復制代碼

 應用條件變量對前面用信號量進行保護的環狀緩沖區的例子進行改進:

 

   下面的例子中:

     1)生產者首先必須檢查緩沖是否已滿(numUsedBytes==BufferSize),如果是,線程停下來等待bufferNotFull條件。如果不是,在緩沖中生產數據,增加numUsedBytes,激活條件 bufferNotEmpty。

     2)使用mutex來保護對numUsedBytes的訪問。

     另外,QWaitCondition::wait()接收一個mutex作為參數,這個mutex應該被調用線程初始化為鎖定狀態。在線程進入休眠狀態之前,mutex會被解鎖。而當線程被喚醒時,mutex會再次處於鎖定狀態。

 

     而且,從鎖定狀態到等待狀態的轉換是原子操作,這阻止了競爭條件的產生。當程序開始運行時,只有生產者可以工作。消費者被阻塞等待bufferNotEmpty條件,一旦生產者在緩沖中放入一個字節,bufferNotEmpty條件被激發,消費者線程於是被喚醒。

復制代碼
 1 const int DataSize = 100000;
 2  const int BufferSize = 8192;
 3  char buffer[BufferSize];
 4 
 5  QWaitCondition bufferNotEmpty;
 6  QWaitCondition bufferNotFull;

 7  QMutex mutex;

 

 

 8  int numUsedBytes = 0;
 9 
10  class Producer : public QThread
11  {
12  public:
13      void run();
14  };
15 
16  void Producer::run()
17  {
18      qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
19 
20      for (int i = 0; i < DataSize; ++i) {

21          mutex.lock();

 

             //producer線程首先檢查緩沖區是否已滿 

22          if (numUsedBytes == BufferSize)//緩沖區已滿,等待consumer來減少numUsedBytes

 

                 // bufferNotFull.wait(&mutex)先調用mutex.unlock()然后收到信號時調用mutex.lock()

23              bufferNotFull.wait(&mutex);//緩沖區已滿等待bufferNotFull的條件變量成立變為有信號
24          mutex.unlock();
25        
26          buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
27 
28          mutex.lock();
29          ++numUsedBytes; //producer用掉一個Bytes,表示producer寫入buffer中的字節數
30          bufferNotEmpty.wakeAll();
31          mutex.unlock();
32      }
33  }
34 
35  class Consumer : public QThread
36  {
37  public:
38      void run();
39  };
40 
41  void Consumer::run()
42  {
43      for (int i = 0; i < DataSize; ++i) {
44          mutex.lock();
45          if (numUsedBytes == 0)
46              bufferNotEmpty.wait(&mutex);
47          mutex.unlock();
48 
49          fprintf(stderr, "%c", buffer[i % BufferSize]);
50 
51          mutex.lock();
52          --numUsedBytes;
53          bufferNotFull.wakeAll();
54          mutex.unlock();
55      }
56      fprintf(stderr, "\n");
57  }
58 
59  int main(int argc, char *argv[])
60  {
61      QCoreApplication app(argc, argv);
62      Producer producer;
63      Consumer consumer;
64      producer.start();
65      consumer.start();
66      producer.wait();
67      consumer.wait();
68      return 0;
69  } 
復制代碼

 另外一個例子:

復制代碼
 1     #include <qapplication.h>
 2     #include <qpushbutton.h>
 3 
 4     // 全局條件變量
 5     QWaitCondition mycond;
 6 
 7     // Worker類實現
 8     class Worker : public QPushButton, public QThread
 9     {
10         Q_OBJECT
11 
12     public:
13         Worker(QWidget *parent = 0, const char *name = 0)
14             : QPushButton(parent, name)
15         {
16             setText("Start Working");
17 
18             // 連接從QPushButton繼承來的信號和我們的slotClicked()方法
19             connect(this, SIGNAL(clicked()), SLOT(slotClicked()));
20 
21             // 調用從QThread繼承來的start()方法……這將立即開始線程的執行
22             QThread::start();
23         }
24 
25     public slots:
26         void slotClicked()
27         {
28             // 喚醒等待這個條件變量的一個線程
29              mycond.wakeOne();
30         }
31 
32     protected:
33         void run()
34         {
35             // 這個方法將被新創建的線程調用……
36 
37             while ( TRUE ) {
38                 // 鎖定應用程序互斥鎖,並且設置窗口標題來表明我們正在等待開始工作
39                 qApp->lock();
40                 setCaption( "Waiting" );
41                 qApp->unlock();
42 
43                 // 等待直到我們被告知可以繼續
44                  mycond.wait();
45 
46                 // 如果我們到了這里,我們已經被另一個線程喚醒……讓我們來設置標題來表明我們正在工作
47                 qApp->lock();
48                 setCaption( "Working!" );
49                 qApp->unlock();
50 
51                 // 這可能會占用一些時間,幾秒、幾分鍾或者幾小時等等,因為這個一個和GUI線程分開的線程,在處理事件時,GUI線程不會停下來……
52                 do_complicated_thing();
53             }
54         }
55     };
56 
57     // 主線程——所有的GUI事件都由這個線程處理。
58     int main( int argc, char **argv )
59     {
60         QApplication app( argc, argv );
61 
62         // 創建一個worker……當我們這樣做的時候,這個worker將在一個線程中運行
63         Worker firstworker( 0, "worker" );
64 
65         app.setMainWidget( &worker );
66         worker.show();
67 
68         return app.exec();
69     }
70 
復制代碼

 

 

7.線程安全類 

 

 

 

 

非線程安全類

 

    這個類不是線程安全的,因為假如多個線程都試圖修改數據成員 n,結果未定義。這是因為c++中的++和--操作符不是原子操作。實際上,它們會被擴展為三個機器指令:
1,把變量值裝入寄存器
2,增加或減少寄存器中的值
3,把寄存器中的值寫回內存
    假如線程A與B同時裝載變量的舊值,在寄存器中增值,回寫。他們寫操作重疊了,導致變量值僅增加了一次。很明顯,訪問應該串行化:A執行123步驟時不應被打斷。

復制代碼
class Counter
{
  public:
      Counter() {n=0;}
      void increment() {++n;}
      void decrement() {--n;}
      int value() const {return n;}
 private:
      int n;
};
復制代碼

 

 

線程安全類

 

復制代碼
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函數。

http://www.cnblogs.com/NeuqUstcIim/archive/2008/08/02/1258871.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM