一.系列總連結:
QT5.14.2 官方例子 - 學習系列
https://blog.csdn.net/qq_22122811/article/details/108007519
二.項目位置:
Examples\Qt-5.14.2\corelib\threads\semaphores
注:在Examples下的路徑
項目子產品:corelib\threads
2.1: 資源下載下傳:
管道1:
下載下傳qtcreator源碼,會附帶該例程;
管道2:
github下載下傳連結:
https://github.com/peterwei24/QT5.14.2_Examples/tree/master/allExamplesCode/corelib/threads/semaphores
三.項目描述:
示範了生産者往緩存中寫入資料,通過信号量保持消費者讀取資料的同時性。
項目效果:

這個是官方例子的顯示結果,不太友善明白過程,我修改了一下,顯示結果如下:
大緻能看到過程是圍繞着生産者生産完,消費者開始消費的過程,這就符合通過信号量來保持線程間互斥性的設計;
四.官網講解:
https://doc.qt.io/qt-5.14/qtcore-threads-semaphores-example.html
示範了使用Qt的多線程程式設計。
生産者将資料寫入緩沖區,直到它到達緩沖區的末尾,然後從頭開始重新啟動,覆寫現有的資料。消費者線程在生成資料時讀取資料并将其寫入标準錯誤。
信号量使它可能擁有比互斥鎖更進階别的并發性。如果對緩沖區的通路是由QMutex保護的,那麼消費者線程不能與産生線程同時通路緩沖區。不過,讓兩個線程同時處理緩沖區的不同部分也沒有什麼壞處。
該示例包含兩個類:生産者和消費者。兩者都繼承自QThread。用于這兩個類之間通信的循環緩沖區和保護它的信号量是全局變量。
使用QSemaphore來解決生産者-消費者問題的另一種方法是使用QWaitCondition和QMutex。這就是等待條件示例所做的事情。
五.思路及邏輯解析:
(對例子進行了部分修改,友善閱讀和了解,和項目描述中修改結果一緻!)
全局變量:
const int DataSize = 10;
const int BufferSize = 3;
char buffer[BufferSize];
QSemaphore freeBytes(BufferSize);
QSemaphore usedBytes;
DataSize是生産者将生成的資料量。為了使這個例子盡可能簡單,我們把它設為常數。BufferSize是循環緩沖區的大小。它小于DataSize,這意味着在某個時間點,生産者将到達緩沖區的末尾,并從開始重新開始。
為了同步生産者和消費者,我們需要兩個信号量。freeBytes信号量控制緩沖區的“空閑”區域(生産者還沒有填充資料或者消費者已經讀取的區域)。usedBytes信号量控制緩沖區的“已使用”區域(生産者已經填充但消費者還沒有讀取的區域)。
這些信号量一起確定生産者永遠不會超過消費者之前的BufferSize位元組,并且消費者永遠不會讀取生産者還沒有生成的資料。
freeBytes信号量是用BufferSize初始化的,因為最初整個緩沖區是空的。usedBytes信号量初始化為0(如果沒有指定,則為預設值)。
生産者類:
class Producer : public QThread
{
public:
void run() override
{
for (int i = 0; i < DataSize; ++i) {
// 目前信号量是否有可以填充資料的緩沖區,如果資源計數為0,則沒有可以填充的緩沖區,
// 那麼該函數會阻塞目前線程
freeBytes.acquire();
buffer[i % BufferSize] = "ACGT"[QRandomGenerator::global()->bounded(4)];
// 使useSpace對象的資源計數加1,此時消費者線程就有機會取資料了
usedBytes.release();
}
}
};
生産者生成資料的DataSize位元組。在将一個位元組寫入循環緩沖區之前,它必須使用freeBytes信号量獲得一個“空閑”位元組。如果消費者沒有跟上生産者的進度,QSemaphore::acquire()調用可能會阻塞。(即消費沒跟上,生産就會一定時間後停滞!)
最後,生産者使用usedBytes信号量釋放一個位元組。已成功地将“空閑”位元組轉換為“已使用”位元組,準備由使用者讀取。
補充:
參照:https://blog.csdn.net/hitzsf/article/details/109105288
1.QRandomGenerator: 允許從高品質随機數生成器擷取随機值。
QRandomGenerator :: global()傳回QRandomGenerator的全局執行個體,Qt将確定該執行個體被安全地播種。
-
quint32 bounded(quint32 highest)
生成[0,hightest) 範圍内的quint32 類型的随機的整數
2.void QSemaphore::acquire(int n = 1):試圖擷取由信号量保護的n個資源。如果n > available(),這個調用将阻塞,直到有足夠的資源可用。
3.void QSemaphore::release(int n = 1):釋放由信号量保護的n個資源。這個函數也可以用來“建立”資源。例如:
消費者類:
class Consumer : public QThread
{
Q_OBJECT
public:
void run() override
{
for (int i = 0; i < DataSize; ++i) {
// 目前信号量是否有可以取資料的緩沖區,如果資源計數為0,則沒有可取的緩沖區,那麼該函數
// 會阻塞目前線程
usedBytes.acquire();
fprintf(stderr, "%c", buffer[i % BufferSize]);
// 使freeSpace對象的資源計數加1,此時生産者線程就有機會生産資料了
freeBytes.release();
}
fprintf(stderr, "\n");
}
};
代碼與生産者非常相似,除了這一次我們獲得一個“已使用”位元組并釋放一個“空閑”位元組,而不是相反。
main()函數:
在main()中,我們建立了兩個線程,并調用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;
}
六.知識鞏固
https://www.cnblogs.com/alinh/p/6905221.html
信号量的了解
“信号量用在多線程多任務同步的,一個線程完成了某一個動作就通過信号量告訴别的線程,别的線程再進行某些動作(大家都在semtake的時候,就阻塞在 哪裡)。而互斥鎖是用在多線程多任務互斥的,一個線程占用了某一個資源,那麼别的線程就無法通路,直到這個線程unlock,其他的線程才開始可以利用這 個資源。比如對全局變量的通路,有時要加鎖,操作完了,在解鎖。有的時候鎖和信号量會同時使用的”
也就是說,信号量不一定是鎖定某一個資源,而是流程上的概念,比如:有A,B兩個線程,B線程要等A線程完成某一任務以後再進行自己下面的步驟,這個任務 并不一定是鎖定某一資源,還可以是進行一些計算或者資料處理之類。而線程互斥量則是“鎖住某一資源”的概念,在鎖定期間内,其他線程無法對被保護的資料進 行操作。在有些情況下兩者可以互換。
互斥量和信号量的差別
1. 互斥量用于線程的互斥,信号量用于線程的同步。
這是互斥量和信号量的根本差別,也就是互斥和同步之間的差別。
互斥:是指某一資源同時隻允許一個通路者對其進行通路,具有唯一性和排它性。但互斥無法限制通路者對資源的通路順序,即通路是無序的。
同步:是指在互斥的基礎上(大多數情況),通過其它機制實作通路者對資源的有序通路。在大多數情況下,同步已經實作了互斥,特别是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個通路者同時通路資源。
2. 互斥量值隻能為0/1,信号量值可以為非負整數。(這個似乎不太好了解,信号量實作多個同類資源的多線程互斥和同步)
也就是說,一個互斥量隻能用于一個資源的互斥通路,它不能實作多個資源的多線程互斥問題。信号量可以實作多個同類資源的多線程互斥和同步。當信号量為單值信号量是,也可以完成一個資源的互斥通路。
3. 互斥量的加鎖和解鎖必須由同一線程分别對應使用,信号量可以由一個線程釋放,另一個線程得到。