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
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();
24 }
25
Qt執行緒同步
1.QMutex
QMutex ( bool recursive = FALSE )
void lock () //試圖鎖定互斥量。如果另一個執行緒已經鎖定這個互斥量,那麼這次呼叫將阻塞 直到那個執行緒把它解鎖。
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:
1int complexFunction(int flag)2 {
3 QMutexLocker locker(&mutex);
4 5int retVal =0;
6 7switch (flag) {
8case0:
9case1:
10return moreComplexFunction(flag);
11case2:
12 {
13int status = anotherFunction();
14if (status <0)
15return-2;
16 retVal = status + flag;
17 }
18break;
19default:
20if (flag >10)
21return-1;
22break;
23 }
2425return 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()
{
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 的一般化,它可以保護一定數量的相同資源,與此相對,一個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.
- returns immediately if it cannot acquire the resources
- returns the number of available resources at any time.
Example:
QSemaphore sem( 5 ); // sem.available() == 5sem.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執行緒在此區域寫入資料 ,初始資源數量為BufferSize7 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)
12class Producer : public QThread13 {
14public:
15void run();
16 };
17 //生產者每acquire一次就,使用掉Buffer個資源中的一個,而寫入的字元存入到buffer陣列中
//從而消費者可用讀取字元,從而消費者獲取一個資源
18void Producer::run()19 {
20//qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));21for (int i =0; i < DataSize; ++i) {
22 freeBytes.acquire ();
23 buffer[i % BufferSize] ="ACGT"[(int)qrand() %4];
24 usedBytes.release ();
25 }
26 }
2728class Consumer : public QThread
29 {
30public:
31void run();
32 };
3334void Consumer::run()
35 {
36for (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.
46int 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();
55return0;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
- bool wait ( QMutex * mutex, unsigned long time = ULONG_MAX )
-
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中導致引起