http://dev.wo.com.cn/bbs/viewthread.jsp?tid=159430
Qt 線程同步執行個體介紹是本文介紹的內容,在Qt中使用線程,沒有Mfc中那麼繁瑣,它提供了QThread線程類,提供了建立一個新的方法。線程通過重載QThread::run()函數來完成其操作的,這一點與Java中的線程類相似。
實現一個簡單的繼承自QThread的使用者線程類,代碼如下。
- classThread:publicQThread
- {
- public:
- Thread();
- voidstop();
- protected:
- virtualvoidrun();
- private:
- boolm_stop;
- };
- Thread::Thread()
- {
- m_stop=false;
- }
- voidThread::stop()
- {
- m_stop=true;
- }
- voidThread::run()
- {
- while(!m_stop)
- {
- sleep(1);
- qDebug("vic.MINg!");
- }
- qDebug("end!");
- }
在以上的樣本中可以看出,線程的編寫並不難!
啟動線程的時候可以,調用函數QThread::start(),開始Thread線程對象。
停止線程的時候可以,調用函數QThread::terminate(),但是terminate()函數並不會立刻終止線程,該線程何時終止取決於作業系統的調度策略。需要注意的是,terminate()函數過於毒辣,它可能線上程執行的任意一步終止執行,從而產生不可預知的後果(如修改某個重要資料時),另外,它也沒有給線程任何清理現場的機會(如釋放記憶體和鎖等)。
因此,停止線程可以,如上代碼所示,手寫函數stop(),使其線程柔和的退出。
線程停止後,應調用QThread::wait()函數,它使的線程阻塞等待直到退出或逾時。
貌似在Unix或Linux下編譯多線程應用程式還必須在.pro檔案中加入如下一行,它告訴qmake使用Qt庫中的線程版本。Windows上,Qt庫預設就是線程的。
CONFIG += thread
介紹完了線程的建立,接下來走入正題了,多線程應用程式的一個最普通的需求就是同步幾個線程。Qt提供了以下幾個類來完成這一點:QMutex、QMutexLocker、QSemphore、QWaitCondition。
當然可能還包含QReadWriteLocker、QReadLocker、QWriteLocker,但線程同步是應用很少,這裡只做簡單的講解!
QMutex、QMutexLocker
QMutex類提供了一個保護一段臨界區代碼的方法,他每次只允許一個線程訪問這段臨界區代碼。QMutex::lock()函數用來鎖住互斥量,如果互斥量處於解鎖狀態,當前線程就會立即抓住並鎖定它;否則當前線程就會被阻塞,直到持有這個互斥量的線程對其解鎖。線程調用lock()函數後就會持有這個互斥量直到調用unlock()操作為止。QMutex還提供了一個tryLock()函數,如果互斥量已被鎖定,就立即返回。
現在使用QMutex保護上面的線程類的m_stop布爾變數,雖然沒啥用,但這裡的目的只是為了示範下QMutex的用法~~
- //thread.h標頭檔,添加互斥量對象
- private:
- ...
- QMutexmutex;
- };
- voidThread::run()
- {
- forever{
- mutex.lock();
- if(m_stop){
- m_stop=false;
- mutex.unlock();
- break;
- }
- mutex.unlock();
- qDebug("vic.MINg!");
- }
- qDebug("end!");
- }
- voidThread::stop()
- {
- mutex.lock();
- m_stop=true;
- mutex.unlock();
- }
在這裡QMutex能夠完全完成互斥操作,但是有些情況下QMutex類是無法某些特定的互斥操作的,下面舉個例子:
這裡我們把void stop()函數,重新定義下,讓他以布爾形式返回,實際也沒有啥用...只為樣本的示範效果~~
- boolThread::stop()
- {
- m_stop=true;
- returnm_stop;
- }
現在問題出來了,如果要在stop()函數中使用mutex進行互斥操作,但unlock()操作寫在那裡?unlock()操作卻不得不再return之後,從而導致unlock()操作永遠也無法執行...
Qt提供了QMutexLocker類何以簡化互斥量的處理,它在建構函式中接受一個QMutex對象作為參數並將其鎖定,在解構函式中解鎖這個互斥量。
這樣可以像下面這樣重新編寫stop()函數:
- boolThread::stop()
- {
- QMutexLockerlocker(&mutex);
- m_stop=true;
- returnm_stop;
- }
QReadWriteLocker、QReadLocker、QWriteLocker
下面是一段對QReadWriteLocker類的對象進行,讀寫鎖的操作,比較簡單,這裡也不多做講解了,自己看吧
- MyDatadata;
- QReadWriteLocklock;
- voidReaderThread::run()
- {
- ...
- lock.lockForRead();
- access_data_without_modifying_it(&data);
- lock.unlock();
- ...
- }
- voidWriterThread::run()
- {
- ...
- lock.lockForWrite();
- modify_data(&data);
- lock.unlock();
- ...
- }
QSemphore
Qt中的訊號量是由QSemaphore類提供的,訊號量可以理解為互斥量功能的擴充,互斥量只能鎖定一次而訊號量可以擷取多次,它可以用來保護一定數量的同種資源。
acquire(n)函數用於擷取n個資源,當沒有足夠的資源時調用者將被阻塞直到有足夠的可用資源。release(n)函數用於釋放n個資源。
QSemaphore類還提供了一個tryAcquire(n)函數,在沒有足夠的資源是該函數會立即返回。
一個典型的訊號量應用程式是在兩個線程間傳遞一定數量的資料(DataSize),而這兩個線程使用一定大小(BufferSize)的共用迴圈緩衝。
- constintDataSize=100000;
- constintBufferSize=4096;
- charbuffer[BufferSize];
生產者線程向緩衝中寫入資料,直到它到達終點,然後在起點重新開始,覆蓋已經存在的資料。消費者線程讀取前者產生的資料。
生產者、消費者執行個體中對同步的需求有兩處,如果生產者過快的產生資料,將會覆蓋消費者還沒有讀取的資料,如果消費者過快的讀取資料,將越過生產者並且讀取到一些垃圾資料。
解決這個問題的一個有效方法是使用兩個訊號量:
- QSemaphorefreeSpace(BufferSize);
- QSemaphoreusedSpace(0);
freeSpace訊號量控制生產者可以填充資料的緩衝部分。usedSpace訊號量控制消費者可以讀取的地區。這兩個訊號量是互補的。其中freeSpace訊號量被初始化為BufferSize(4096),表示程式一開始有BufferSize個緩衝區單元可被填充,而訊號量usedSpace被初始化為0,表示程式一開始緩衝區中沒有資料可供讀取。
對於這個執行個體,每個位元組就看作一個資源,實際應用中常會在更大的單位上進行操作,從而減小使用訊號量帶來的開銷。
- voidProducer::run()
- {
- for(inti=0;i<DataSize;++i){
- freeSpace.acquire();
- buffer[i%BufferSize]="MING"[uint(rand())%4];
- usedSpace.release();
- }
- }
在生產者中,我們從擷取一個“自由的”位元組開始。如果緩衝被消費者還沒有讀取的資料填滿,acquire()的調用就會阻塞,直到消費者已經開始消耗這些資料為止。一旦我們已經擷取了這個位元組,我們就用一些隨機資料("M"、"I"、"N"或"G")填充它並且把這個位元組釋放為“使用的”,所以它可以被消費者線程使用。
- voidConsumer::run()
- {
- for(inti=0;i<DataSize;++i){
- usedSpace.acquire();
- cerr<<buffer[i%BufferSize];
- freeSpace.release();
- }
- cerr<<endl;
- }
在消費者中,我們從擷取一個“使用的”位元組開始。如果緩衝中沒有包含任何可讀的資料,acquire()調用將會阻塞,直到生產者已經產生一些資料。一旦我們已經擷取了這個位元組,我們就列印它並且把這個位元組釋放為“自由的”,使它可以被生產者使用來再次填充資料。
- intmain()
- {
- Producerproducer;
- Consumerconsumer;
- producer.start();
- consumer.start();
- producer.wait();
- consumer.wait();
- return0;
- }
main()函數的功能比較簡單,負責啟動生產者和消費者線程,然後等待其各自執行完畢後自動結束。
QWaitCondition
對生產者和消費者問題的另一個解決方案是使用QWaitCondition,它允許線程在一定條件下喚醒其他線程。其中wakeOne()函數在條件滿足時隨機喚醒一個等待線程,而wakeAll()函數則在條件滿足時喚醒所有等待線程。
下面重寫生產者和消費者執行個體,以QMutex為等待條件,QWaitCondition允許一個線程在一定條件下喚醒其他線程。
- constintDataSize=100000;
- constintBufferSize=4096;
- charbuffer[BufferSize];
- QWaitConditionbufferIsNotFull;
- QWaitConditionbufferIsNotEmpty;
- QMutexmutex;
- intusedSpace=0;
在緩衝之外,我們聲明了兩個QWaitCondition、一個QMutex和一個儲存了在緩衝中有多少個“使用的”位元組的變數。
- voidProducer::run()
- {
- for(inti=0;i<DataSize;++i){
- mutex.lock();
- if(usedSpace==BufferSize)
- bufferIsNotFull.wait(&mutex);
- buffer[i%BufferSize]="MING"[uint(rand())%4];
- ++usedSpace;
- bufferIsNotEmpty.wakeAll();
- mutex.unlock();
- }
- }
在生產者中,我們從檢查緩衝是否充滿開始。如果是充滿的,我們等待“緩衝不是充滿的”條件。當這個條件滿足時,我們向緩衝寫入一個位元組,增加usedSpace,並且在喚醒任何等待這個“緩衝不是空白的”條件變為真的線程。
for迴圈中的所有語句需要使用互斥量加以保護,以保護其操作的原子性。
- boolwait(QMutex*mutex,unsignedlongtime=ULONG_MAX);
這個函數做下說明,該函數將互斥量解鎖並在此等待,它有兩個參數,第一個參數為一個鎖定的互斥量,第二個參數為等待時間。如果作為第一個參數的互斥量在調用是不是鎖定的或出現遞迴鎖定的情況,wait()函數將立即返回。
調用wait()操作的線程使得作為參數的互斥量在調用前變為鎖定狀態,然後自身被阻塞變成為等待狀態直到滿足以下條件:
其他線程調用了wakeOne()或者wakeAll()函數,這種情況下將返回"true"值。
第二個參數time逾時(以毫秒記時),該參數預設情況是ULONG_MAX,表示永不逾時,這種情況下將返回"false"值。
wait()函數返回前會將互斥量參數重新設定為鎖定狀態,從而保證從鎖定狀態到等待狀態的原則性轉換。
- voidConsumer::run()
- {
- forever{
- mutex.lock();
- if(usedSpace==0)
- bufferIsNotEmpty.wait(&mutex);
- cerr<<buffer[i%BufferSize];
- --usedSpace;
- bufferIsNotFull.wakeAll();
- mutex.unlock();
- }
- cerr<<endl;
- }
消費者做的和生產者正好相反,他等待“緩衝不是空白的”條件並喚醒任何等待“緩衝不是充滿的”的條件的線程。
main()函數與上面的基本相同,這個不再多說。
在QThread類的靜態函數currentThread(),可以返回當前線程的線程ID。在X11環境下,這個ID是一個unsigned long類型的值。
小結:關於 Qt 線程同步執行個體介紹的內容介紹完了,希望本文對你有所協助。