Qt多線程
Qt線程類
Qt 包含下面一些線程相關的類:
Qt線程的創(chuàng)建
Qt線程中有一個公共的抽象類,,所有的線程都是從這個QThread抽象類中派生的,要實現(xiàn)QThread中的純虛函數(shù)run(),run()函數(shù)是通過start()函數(shù)來實現(xiàn)調(diào)用的,。
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();//自動調(diào)用run(),否則即使該線程創(chuàng)建,,也是一開始就掛起 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 () //試圖鎖定互斥量。如果另一個線程已經(jīng)鎖定這個互斥量,,那么這次調(diào)用將阻塞直到那個線程把它解鎖,。
void unlock ()
bool locked ()
bool tryLock () //如果另一個進程已經(jīng)鎖定了這個互斥量,這個函數(shù)返回假,,而不是一直等到這個鎖可用為止,,比如,它不是阻塞的,。
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的術(shù)語,,這段代碼應該是: 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來實現(xiàn)多個線程讀,一個線程寫,。寫線程執(zhí)行的時候會阻塞所有的讀線程,,而讀線程之間的運行不需要進行同步。
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"訪問進行區(qū)別對待,。它使得多個讀者可以共時訪問數(shù)據(jù)。使用QReadWriteLock而不是QMutex,,可以使得多線程程序更具有并發(fā)性,。
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() { QReadLocker locker(&lock); return data; } It is equivalent to the following code:
QReadWriteLock lock;
QByteArray readData() { lock.lockForRead(); lock.unlock(); return data; }
5.QSemaphore
QSemaphore 是QMutex的一般化,,它可以保護一定數(shù)量的相同資源,與此相對,,一個mutex只保護一個資源,。下面例子中,使用QSemaphore來控制對環(huán)狀緩沖區(qū)的訪問,,此緩沖區(qū)被生產(chǎn)者線程和消費者線程共享,。生產(chǎn)者不斷向緩沖寫入數(shù)據(jù)直到緩沖末端,消費者從緩沖不斷從緩沖頭部讀取數(shù)據(jù),。 信號量比互斥量有更好的并發(fā)性,,假如我們用互斥量來控制對緩沖的訪問,那么生產(chǎn)者,,消費者不能同時訪問緩沖,。然而,我們知道在同一時刻,,不同線程訪問緩沖的不同部分并沒有什么危害,。 QSemaphore semaphore(1); | QMutex mutex; Qsemaphore.acquire(); | Qmutex.lock();
Qsemaphore.release(); | Qmutex.unlock(); Public Functions
Semaphores support two fundamental operations, acquire() and release():
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
生產(chǎn)者線程寫數(shù)據(jù)到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線程在此區(qū)域?qū)懭霐?shù)據(jù),,初始資源數(shù)量為BufferSize 7 QSemaphore usedBytes; //consumer線程讀取此區(qū)域的數(shù)據(jù),初始資源數(shù)量為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 //生產(chǎn)者每acquire一次就,,使用掉Buffer個資源中的一個,而寫入的字符存入到buffer數(shù)組中
//從而消費者可用讀取字符,從而消費者獲取一個資源
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. 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函數(shù): 當producer線程執(zhí)行run函數(shù),,如果buffer中已經(jīng)滿了,,而沒有consumer線程沒有讀,這樣producer就不能再往buffer 中寫字符,。此時在 freeBytes.acquire處就阻塞直到consumer線程讀(consume)數(shù)據(jù),。一旦producer獲取到一個字節(jié)(資源) 就寫如一個隨機的字符,并調(diào)用 usedBytes.release從而consumer線程獲取一個資源可以讀一個字節(jié)的數(shù)據(jù)了,。 consumer的run函數(shù): 當consumer線程執(zhí)行run函數(shù),,如果buffer中沒有數(shù)據(jù),就是資源=0,,則consumer線程在此處阻塞,。直到producer線程執(zhí)行 寫操作,寫入一個字節(jié),,并執(zhí)行usedBytes.release從而使得consumer線程的可用資源數(shù)=1,。則consumer線程從阻塞狀態(tài)中退出, 并將 usedBytes資源數(shù)-1,,當前資源數(shù)=0,。
6.QWaitCondition
假定每次用戶按下一個鍵,我們有三個任務要同時執(zhí)行,,每個任務都可以放到一個線程中,,每個線程的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()將會從這個方法中返回并繼續(xù)執(zhí)行 key_pressed.wakeAll(); }
注意這三個線程被喚醒的順序是未定義的,并且當鍵被按下時,,這些線程中的一個或多個還在do_something(),,它們將不會被喚醒(因為它們現(xiàn)在沒有等待條件變量)并且這個任務也就不會針對這次按鍵執(zhí)行操作。這種情況是可以避免得,,比如,,就像下面這樣做:
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 // 睡眠,直到?jīng)]有忙碌的工作線程才醒來,。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.wakeAll(); 29 } 30 應用條件變量對前面用信號量進行保護的環(huán)狀緩沖區(qū)的例子進行改進:
下面的例子中: 1)生產(chǎn)者首先必須檢查緩沖是否已滿(numUsedBytes==BufferSize),,如果是,線程停下來等待bufferNotFull條件,。如果不是,,在緩沖中生產(chǎn)數(shù)據(jù),增加numUsedBytes,激活條件 bufferNotEmpty,。 2)使用mutex來保護對numUsedBytes的訪問,。 另外,QWaitCondition::wait()接收一個mutex作為參數(shù),,這個mutex應該被調(diào)用線程初始化為鎖定狀態(tài),。在線程進入休眠狀態(tài)之前,mutex會被解鎖,。而當線程被喚醒時,,mutex會再次處于鎖定狀態(tài)。
而且,,從鎖定狀態(tài)到等待狀態(tài)的轉(zhuǎn)換是原子操作,,這阻止了競爭條件的產(chǎn)生,。當程序開始運行時,只有生產(chǎn)者可以工作,。消費者被阻塞等待bufferNotEmpty條件,,一旦生產(chǎn)者在緩沖中放入一個字節(jié),bufferNotEmpty條件被激發(fā),,消費者線程于是被喚醒,。
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線程首先檢查緩沖區(qū)是否已滿
22 if (numUsedBytes == BufferSize)//緩沖區(qū)已滿,等待consumer來減少numUsedBytes // bufferNotFull.wait(&mutex)先調(diào)用mutex.unlock()然后收到信號時調(diào)用mutex.lock() 23 bufferNotFull.wait(&mutex);//緩沖區(qū)已滿等待bufferNotFull的條件變量成立變?yōu)橛行盘?/span>24 mutex.unlock(); 25 26 buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4]; 27 28 mutex.lock(); 29 ++numUsedBytes; //producer用掉一個Bytes,,表示producer寫入buffer中的字節(jié)數(shù) 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類實現(xiàn) 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 // 調(diào)用從QThread繼承來的start()方法……這將立即開始線程的執(zhí)行 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 // 這個方法將被新創(chuàng)建的線程調(diào)用…… 36 37 while ( TRUE ) { 38 // 鎖定應用程序互斥鎖,,并且設置窗口標題來表明我們正在等待開始工作 39 qApp->lock(); 40 setCaption( "Waiting" ); 41 qApp->unlock(); 42 43 // 等待直到我們被告知可以繼續(xù) 44 mycond.wait(); 45 46 // 如果我們到了這里,我們已經(jīng)被另一個線程喚醒……讓我們來設置標題來表明我們正在工作 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 // 創(chuàng)建一個worker……當我們這樣做的時候,,這個worker將在一個線程中運行 63 Worker firstworker( 0, "worker" ); 64 65 app.setMainWidget( &worker ); 66 worker.show(); 67 68 return app.exec(); 69 } 70
7.線程安全類
非線程安全類
這個類不是線程安全的,,因為假如多個線程都試圖修改數(shù)據(jù)成員
n,結(jié)果未定義。這是因為c++中的++和--操作符不是原子操作,。實際上,,它們會被擴展為三個機器指令:
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類在構(gòu)造函數(shù)中自動對mutex進行加鎖,,在析構(gòu)函數(shù)中進行解鎖,。隨便一提的是,,mutex使用了mutable關鍵字來修飾,,因為我們在value()函數(shù)中對mutex進行加鎖與解鎖操作,而value()是一個const函數(shù),。 |
|