Qt之线程同步(生产者消费者模式 - QWaitCondition)
簡述
生產者將數(shù)據(jù)寫入緩沖區(qū),直到它到達緩沖區(qū)的末尾,這時,它從開始位置重新啟動,覆蓋現(xiàn)有數(shù)據(jù)。消費者線程讀取數(shù)據(jù)并將其寫入標準錯誤。
Wait condition(等待條件)比單獨使用 mutex(互斥量)有一個更高級的并發(fā)性,如果緩沖區(qū)的訪問由一個 QMutex 把守,當生產者線程訪問緩沖區(qū)時,消費者線程將無法訪問。然而,兩個線程同時訪問不同的緩沖區(qū)是沒有害處的。
示例包含兩個類:Producer 和 Consumer,均繼承自 QThread。循環(huán)緩沖區(qū)用于兩個類之間的溝通,同步工具用于保護全局變量。
另一種“生產者 - 消費者”模式的方案是使用 QSemaphore - Qt之線程同步(生產者消費者模式 - QSemaphore)
- 簡述
- 全局變量
- Producer
- Consumer
- main 函數(shù)
- 更多參考
全局變量
首先,一起來看循環(huán)緩沖區(qū)和相關的同步工具:
const int DataSize = 100000;const int BufferSize = 8192; char buffer[BufferSize];QWaitCondition bufferNotEmpty; QWaitCondition bufferNotFull; QMutex mutex; int numUsedBytes = 0;DataSize 是生產者將生成的數(shù)據(jù)數(shù)量,為了讓示例盡可能地簡單,把它定義為一個常數(shù)。BufferSize 是循環(huán)緩沖區(qū)的大小,小于 DataSize,這意味著在某一時刻生產者將達到緩沖區(qū)的末尾,并從開始位置重新啟動。
要同步生產者和消費者,需要兩個 wait 條件和一個 mutex。當生產者生成一些數(shù)據(jù)時,bufferNotEmpty 條件被發(fā)射,告訴消費者可以讀取它了;當消費者讀取一些數(shù)據(jù)時,bufferNotFull 條件被發(fā)射,告訴生產者生成更多的數(shù)據(jù)。numUsedBytes 為緩沖區(qū)中所包含數(shù)據(jù)的字節(jié)數(shù)。
總之,wait 條件、mutex、和 numUsedBytes 計數(shù)器確保生產者不會先于消費者超過 BufferSize 的大小,而消費者永遠不會讀取生產者尚未生成的數(shù)據(jù)。
Producer
Producer 類的代碼如下:
class Producer : public QThread { public:Producer(QObject *parent = NULL) : QThread(parent){}void run() Q_DECL_OVERRIDE{qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));for (int i = 0; i < DataSize; ++i) {mutex.lock();if (numUsedBytes == BufferSize)bufferNotFull.wait(&mutex);mutex.unlock();buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];mutex.lock();++numUsedBytes;bufferNotEmpty.wakeAll();mutex.unlock();}} };生產者生成 DataSize 字節(jié)的數(shù)據(jù)。在往循環(huán)緩沖區(qū)寫入一個字節(jié)之前,它必須先檢測緩沖區(qū)是否已滿(例如,numUsedBytes 等于 BufferSize),如果緩沖區(qū)滿了,線程就會在 bufferNotFull 條件上等待。
最后,生產者增加 numUsedBytes,并且標志 bufferNotEmpty 條件為 true,從而 numUsedBytes 必然大于 0,
我們使用一個 mutex 保護 numUsedBytes 變量的所有訪問。此外,QWaitCondition::wait() 函數(shù)接受一個 mutex 作為其參數(shù)。當線程被置于休眠狀態(tài)之前,該 mutex 被解鎖;當線程被喚醒,該 mutex 被鎖定。此外,為了防止競爭條件發(fā)生,從鎖定狀態(tài)到等待狀態(tài)的過渡具有原子性。
Consumer
現(xiàn)在轉向 Consumer 類:
class Consumer : public QThread {Q_OBJECT public:Consumer(QObject *parent = NULL) : QThread(parent){}void run() Q_DECL_OVERRIDE{for (int i = 0; i < DataSize; ++i) {mutex.lock();if (numUsedBytes == 0)bufferNotEmpty.wait(&mutex);mutex.unlock();fprintf(stderr, "%c", buffer[i % BufferSize]);mutex.lock();--numUsedBytes;bufferNotFull.wakeAll();mutex.unlock();}fprintf(stderr, "\n");}signals:void stringConsumed(const QString &text); };代碼非常類似于生產者,在讀取字節(jié)之前,需要先檢查緩沖區(qū)是否為空(numUsedBytes 為 0),而非它是否為已滿。并且,當它為空時,等待 bufferNotEmpty 條件。在讀取字節(jié)后,減小 numUsedBytes (而非增加),并標志 bufferNotFull 條件(而非 bufferNotEmpty 條件)。
main() 函數(shù)
在 main() 函數(shù)中,我們創(chuàng)建兩個線程,并調用 QThread::wait(),以確保在退出之前,這兩個線程有時間完成。
int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);Producer producer;Consumer consumer;producer.start();consumer.start();producer.wait();consumer.wait();return 0; }當運行這個程序時,會發(fā)生什么呢?
最初,生產者是唯一一個可以做任何事情的線程,消費者阻塞并等待 bufferNotEmpty 條件被發(fā)射(numUsedBytes 是 0)。一旦生產者把一個字節(jié)放入緩沖區(qū),numUsedBytes 就會變?yōu)?BufferSize - 1,并且 bufferNotEmpty 條件被發(fā)射。這時,可能發(fā)生兩件事:要么消費者線程接管和讀取字節(jié),要么生產者開始生成第二個字節(jié)。
此示例中提出的“生產者 - 消費者”模式,適用于編寫高并發(fā)多線程應用。在多處理器計算機中,程序可能比基于 mutex 的方案快達兩倍之多,因為兩個線程可以同一時間在緩沖區(qū)的不同部分處于激活狀態(tài)。
要知道,這些好處并不總能實現(xiàn),加鎖和解鎖一個 QMutex 是需要成本的。在實踐中,可能需要把緩沖區(qū)分為塊,并針對塊操作而非單個字節(jié)。緩沖區(qū)的大小也是一個必須仔細選擇的參數(shù),需要基于實驗。
總結
以上是生活随笔為你收集整理的Qt之线程同步(生产者消费者模式 - QWaitCondition)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第二周学习进度条
- 下一篇: GIS讲堂第二课-地图切片简介以及OL中