Qt 多線程同步與通信
1 多線程同步
Qt提供了以下幾個類來完成這一點:QMutex、QMutexLocker、QSemphore、QWaitCondition。
當然可能還包含QReadWriteLocker、QReadLocker、QWriteLocker,但
線程同步是應用很少,這里只做簡單的講解!
QMutex、QMutexLocker
QMutex類提供了一個保護一段臨界區代碼的方法,他每次只允許一個線程訪問這段臨界區代碼。QMutex::lock()函數用來鎖住互斥量,如果互斥量處於解鎖狀態,當前線程就會立即抓住並鎖定它;否則當前線程就會被阻塞,直到持有這個互斥量的線程對其解鎖。線程調用lock()函數后就會持有這個互斥量直到調用unlock()操作為止。QMutex還提供了一個tryLock()函數,如果互斥量已被鎖定,就立即返回。
在問題出來了,如果要在stop()函數中使用mutex進行互斥操作,但unlock()操作寫在那里?unlock()操作卻不得不再return之后,從而導致unlock()操作永遠也無法執行...
Qt提供了QMutexLocker類何以簡化互斥量的處理,它在構造函數中接受一個QMutex對象作為參數並將其鎖定,在析構函數中解鎖這個互斥量。
bool Thread::stop()
{
QMutexLocker locker(&mutex);
m_stop = true;
return m_stop;
}
QReadWriteLocker
、QReadLocker、QWriteLocker
下面是一段對QReadWriteLocker類的對象進行,讀寫鎖的操作,比較簡單,這里也不多做講解了,自己看吧
MyData data; QReadWriteLock lock; void ReaderThread::run() { ... lock.lockForRead(); access_data_without_modifying_it(&data); lock.unlock(); ... } void WriterThread::run() { ... lock.lockForWrite(); modify_data(&data); lock.unlock(); ... }
QSemphore
Qt中的信號量是由QSemaphore類提供的,信號量可以理解為互斥量功能的擴展,互斥量只能鎖定一次而信號量可以獲取多次,它可以用來保護一定數量的同種資源。
acquire(n)函數用於獲取n個資源,當沒有足夠的資源時調用者將被阻塞直到有足夠的可用資源。release(n)函數用於釋放n個資源。
QSemaphore類還提供了一個tryAcquire(n)函數,在沒有足夠的資源是該函數會立即返回。
一個典型的信號量應用程序是在兩個
線程間傳遞一定數量的數據(DataSize),而這兩個
線程使用一定大小(BufferSize)的共享循環緩存。
const int DataSize = 100000;
const int BufferSize = 4096;
char buffer[BufferSize];
生產者
線程向緩存中寫入數據,直到它到達終點,然后在起點重新開始,覆蓋已經存在的數據。消費者
線程讀取前者產生的數據。
生產者、消費者實例中對
同步的需求有兩處,如果生產者過快的產生數據,將會覆蓋消費者還沒有讀取的數據,如果消費者過快的讀取數據,將越過生產者並且讀取到一些垃圾數據。
解決這個問題的一個有效的方法是使用兩個信號量:
QSemaphore freeSpace(BufferSize);
QSemaphore usedSpace(0);
freeSpace信號量控制生產者可以填充數據的緩存部分。usedSpace信號量控制消費者可以讀取的區域。這兩個信號量是互補的。其中freeSpace信號量被初始化為BufferSize(4096),表示程序一開始有BufferSize個緩沖區單元可被填充,而信號量usedSpace被初始化為0,表示程序一開始緩沖區中沒有數據可供讀取。
對於這個實例,每個字節就看作一個資源,實際應用中常會在更大的單位上進行操作,從而減小使用信號量帶來的開銷。
void Producer::run() { for (int i = 0; i < DataSize; ++i) { freeSpace.acquire(); buffer[i % BufferSize] = "MING"[uint(rand()) % 4]; usedSpace.release(); } }
在生產者中,我們從獲取一個“自由的”字節開始。如果緩存被消費者還沒有讀取的數據填滿,acquire()的調用就會阻塞,直到消費者已經開始消耗這些數據為止。一旦我們已經獲取了這個字節,我們就用一些隨機數據("M"、"I"、"N"或"G")填充它並且把這個字節釋放為“使用的”,所以它可以被消費者線程使用。
void Consumer::run() { for (int i = 0; i < DataSize; ++i) { usedSpace.acquire(); cerr << buffer[i % BufferSize]; freeSpace.release(); } cerr << endl; }
int main() { Producer producer; Consumer consumer; producer.start(); consumer.start(); producer.wait(); consumer.wait(); return 0; }
QWaitCondition
對生產者和消費者問題的另一個解決方法是使用QWaitCondition,它允許
線程在一定條件下喚醒其他
線程。其中wakeOne()函數在條件滿足時隨機喚醒一個等待
線程,而wakeAll()函數則在條件滿足時喚醒所有等待
線程。
下面重寫生產者和消費者實例,以QMutex為等待條件,QWaitCondition允許一個 線程在一定條件下喚醒其他 線程。
下面重寫生產者和消費者實例,以QMutex為等待條件,QWaitCondition允許一個 線程在一定條件下喚醒其他 線程。
const int DataSize = 100000;
const int BufferSize = 4096;
char buffer[BufferSize];
QWaitCondition bufferIsNotFull;
QWaitCondition bufferIsNotEmpty;
QMutex mutex;
int usedSpace = 0;
在緩存之外,我們聲明了兩個QWaitCondition、一個QMutex和一個存儲了在緩存中有多少個“使用的”字節的變量。
void Producer::run() { for (int i = 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循環中的所有語句需要使用互斥量加以保護,以保護其操作的原子性。
bool wait ( QMutex * mutex, unsigned long time = ULONG_MAX );
這個函數做下說明,該函數將互斥量解鎖並在此等待,它有兩個參數,第一個參數為一個鎖定的互斥量,第二個參數為等待時間。如果作為第一個參數的互斥量在調用是不是鎖定的或出現遞歸鎖定的情況,wait()函數將立即返回。
調用wait()操作的
線程使得作為參數的互斥量在調用前變為鎖定狀態,然后自身被阻塞變成為等待狀態直到滿足以下條件:
其他
線程調用了wakeOne()或者wakeAll()函數,這種情況下將返回"true"值。
第二個參數time超時(以毫秒記時),該參數默認情況是ULONG_MAX,表示永不超時,這種情況下將返回"false"值。
wait()函數返回前會將互斥量參數重新設置為鎖定狀態,從而保證從鎖定狀態到等待狀態的原則性轉換。
void Consumer::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類型的值。
自己總結:
原子類型
QAtomicInt QAtomicPointer
2.多線程通信
a . 全局變量 即共享內存
b. 管道
c. 信號槽
3.多進程通信
信號槽 socket 文件映射