一:系列總連結:
QT5.14.2 官方例子 - 學習系列
https://blog.csdn.net/qq_22122811/article/details/108007519
二:項目位置:
Examples\Qt-5.14.2\corelib\threads\waitconditions
注:在Examples下的路徑
項目子產品:corelib\threads
2.1: 資源下載下傳:
管道1:
下載下傳qtcreator源碼,會附帶該例程;
管道2:
github下載下傳連結:
https://doc.qt.io/qt-5.14/qtcore-threads-waitconditions-example.html
三:項目描述:
和例子Semaphores Example類似,
生産者将資料寫入緩沖區,直到它到達緩沖區的末尾,然後從頭開始重新啟動,覆寫現有的資料。消費者線程在生成資料時讀取資料并将其寫入标準錯誤。
等待條件使它有可能擁有比單獨使用互斥鎖更高的并發級别。如果對緩沖區的通路隻是由QMutex保護,那麼消費者線程不能與産生線程同時通路緩沖區。不過,讓兩個線程同時處理緩沖區的不同部分也沒有什麼壞處。
該示例包含兩個類:生産者和消費者。兩者都繼承自QThread。用于這兩個類之間通信的循環緩沖區和保護它的同步工具是全局變量。
使用QWaitCondition和QMutex來解決生産者-消費者問題的另一種方法是使用QSemaphore。這就是信号燈的例子所做的。
官方例子效果:略
修改官方例子結果:

四:官網講解:
https://doc.qt.io/qt-5/qtcore-threads-waitconditions-example.html
五:解析:
全局變量:
const int DataSize = 5;
const int BufferSize = 2;
char buffer[BufferSize];
QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;
QMutex mutex;
int numUsedBytes = 0;
DataSize是生産者将生成的資料量。為了使這個例子盡可能簡單,我們把它設為常數。BufferSize是循環緩沖區的大小。它小于DataSize,這意味着在某個時間點,生産者将到達緩沖區的末尾,并從開始重新開始。
要同步生産者和消費者,我們需要兩個等待條件和一個互斥量。當生産者生成了一些資料時,bufferNotEmpty條件就會發出信号,告訴消費者它可以開始讀取資料了。bufferNotFull條件在消費者讀取了一些資料後發出信号,告訴生産者它可以生成更多的資料。numUsedBytes是緩沖區中包含資料的位元組數。
等待條件、互斥鎖和numUsedBytes計數器一起確定生産者永遠不會超過消費者之前的BufferSize位元組,并且消費者永遠不會讀取生産者還沒有生成的資料。
生産者:
class Producer : public QThread
{
public:
Producer(QObject *parent = NULL) : QThread(parent)
{
}
// override: 重寫父類的run()函數
void run() override
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
// 擷取numUsedBytes,判斷是否Buffer是否已被填滿
if (numUsedBytes == BufferSize)
// 如果被填滿,則該線程将等待bufferNotFull條件。
bufferNotFull.wait(&mutex);
mutex.unlock();
// 填入資料
buffer[i % BufferSize] = "ACGT"[QRandomGenerator::global()->bounded(4)];
qDebug() << "===================== Producer: " << i << "-" << i % BufferSize << "-" << buffer[i % BufferSize];
// 確定對numUsedBytes通路的唯一性
mutex.lock();
// 記錄緩存區被使用次數+1
++numUsedBytes;
// 喚醒目前的bufferNotEmpty和其他線程的等條件,也可以了解通知bufferNotEmpty為真
bufferNotEmpty.wakeAll();
mutex.unlock();
}
}
};
生産者生成資料的DataSize位元組。在向循環緩沖區寫入一個位元組之前,它必須首先檢查緩沖區是否已滿(即numUsedBytes = BufferSize)。如果緩沖區已滿,線程将等待bufferNotFull條件。
最後,生産者增加numUsedBytes,并通知條件bufferNotEmpty為真,因為numUsedBytes必須大于0。
我們使用互斥鎖來保護對numUsedBytes變量的所有通路。另外,QWaitCondition::wait()函數接受一個互斥鎖作為它的參數。這個互斥鎖線上程進入睡眠狀态之前被解鎖,線上程醒來時被鎖定。此外,從鎖定狀态到等待狀态的轉換是原子的(最小機關),以防止發生競争條件。
補充:
1.void QWaitCondition::wakeAll():
喚醒所有等待等待條件的線程。線程被喚醒的順序取決于作業系統的排程政策,無法控制或預測。
消費者:
class Consumer : public QThread
{
Q_OBJECT
public:
Consumer(QObject *parent = NULL) : QThread(parent)
{
}
void run() override
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
// 判斷numUsedBytes是否為空,即生産者是否填入資料,如果沒有則等待
if (numUsedBytes == 0)
bufferNotEmpty.wait(&mutex);
mutex.unlock();
// 讀取資料
//fprintf(stderr, "%c", buffer[i % BufferSize]);
qDebug() << "Consumer: " << i << "-" << i % BufferSize << "-" << buffer[i % BufferSize];
mutex.lock();
// 讀取了該位元組,則對numUsedBytes減1
--numUsedBytes;
// 等條件bufferNotFull喚醒所有線程中的等條件
bufferNotFull.wakeAll();
mutex.unlock();
}
fprintf(stderr, "\n");
}
signals:
void stringConsumed(const QString &text);
};
代碼與生産者非常相似。在讀取位元組之前,我們檢查緩沖區是否為空(numUsedBytes為0),而不是是否已滿,如果緩沖區為空,則等待bufferNotEmpty條件。在讀取位元組後,對numUsedBytes進行遞減(而不是遞增),并發出bufferNotFull條件(而不是bufferNotEmpty條件)的信号。
main()函數:
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
// run()傳回時,則退出
producer.wait();
consumer.wait();
return 0;
}
六:和semaphore example對比:
從輸出結果看效果一緻,按順序生産者生産(填充buffer資料),然後消費者消費(讀取buffer裡的資料);
在semaphore example中,設計上更易于了解,Producer線程中通過擷取未使用的信号量,接着填充緩存,然後釋放使用過的信号量,這是Consumer擷取使用過的信号量,讀取緩存,接着釋放未使用的信号量,這樣實作了依次寫入和讀取的操作;
在目前wait exmaple中,通過QWaitCondition和QMutex共同促成生産者生産,消費者消費的邏輯順序;其中QMutex的作用是防止兩個線程同時對numUsedBytes和bufferNotFull或bufferNotEmpty進行寫,讀操作;
七:借鑒思路:
采用QMutex和QWaitCondition完成對類似于“生産者-消費者”模型的設計;
八:深入了解
該例子說需要用QMutex和QWaitCondition聯合使用,來完成 "生産-消費"的邏輯過程,現在通過反證法來加深了解,我就認為該例子是不對的,不應該加鎖:
1.将鎖有關操作全去掉,結果如圖:
分析:消費者和生産者先後開始運作run(),但消費者因為numUsedBytes=0,是以在阻塞中,隻列印第2行,但生産者在生産中,buffer的尺寸最後也達到了阻塞的條件numUsedBytes == BufferSize,也開始阻塞,兩個線程就都會陷入阻塞狀态!!
結論:加鎖是為了防止兩個地方同時通路同一塊記憶體,出現通路不同步;加鎖的目的是為了解決這個問題,解決兩個地方對numUsedBytes和bufferNotFull;