信號量( Semaphore ) 是另一種限制對共享資源進行訪問的線程同步機制,它與互斥量 Mutex 相似,但是有區別 。一個互斥量只 能被鎖定一次,而信號量可 以多次使用 。信號量通常用來保護一定數量的相同的資源,如數據採集時的雙緩衝區 。
QSemaphore 是實現信號量功能的類,它提供以下幾個基本 的函數:
- acquire(int n)嘗試獲得 n 個資源。如果沒有這麼多資源,線程將阻塞直到有 n 個資源可用 。
- releas(int n) 釋放 n 個資源, 如果信號量的資源己全部可用 之後再 release(),就可以創建更多的資源,增加可用資源的個數 ;
- int available()返回 當前信號量可用的資源個數.
- bool tryAcquire(int n = 1),嘗試獲取 n 個資源,不成功時不阻塞線程 。
讓我們先看圖吧:
鏈接:本文例子 提取碼:uepl
有關信號量,其實跟互斥量一個意思,只是可以多信號資源,不只是等到互斥解鎖,直接來上代碼部分吧。
qmythread.h 頭文件
#ifndef QMYTHREAD_H
#define QMYTHREAD_H
//#include <QObject>
#include <QThread>
//#include <QMutex>
class QThreadDAQ : public QThread
{
Q_OBJECT
private:
bool m_stop=false; //停止線程
protected:
void run() Q_DECL_OVERRIDE;
public:
QThreadDAQ();
void stopThread();
};
class QThreadShow : public QThread
{
Q_OBJECT
private:
bool m_stop=false; //停止線程
protected:
void run() Q_DECL_OVERRIDE;
public:
QThreadShow();
void stopThread();
signals:
void newValue(int *data,int count, int seq);
};
#endif // QMYTHREAD_H
qmythread.cpp
#include "qmythread.h"
#include <QSemaphore>
//#include <QTime>
const int BufferSize = 8;
static int buffer1[BufferSize];
static int buffer2[BufferSize];
static int curBuf=1; //當前正在寫入的Buffer
static int bufNo=0; //採集的緩衝區序號
static quint8 counter=0;//數據生成器
static QSemaphore emptyBufs(2);//信號量:空的緩衝區個數,初始資源個數爲2
static QSemaphore fullBufs; //滿的緩衝區個數,初始資源爲0
QThreadDAQ::QThreadDAQ()
{
}
void QThreadDAQ::stopThread()
{
// QMutexLocker locker(&mutex);
m_stop=true;
}
void QThreadDAQ::run()
{
m_stop=false;//啓動線程時令m_stop=false
bufNo=0;//緩衝區序號
curBuf=1; //當前寫入使用的緩衝區
counter=0;//數據生成器
int n=emptyBufs.available();
if (n<2) //保證 線程啓動時emptyBufs.available==2
emptyBufs.release(2-n);
while(!m_stop)//循環主體
{
emptyBufs.acquire();//獲取一個空的緩衝區
for(int i=0;i<BufferSize;i++) //產生一個緩衝區的數據
{
if (curBuf==1)
buffer1[i]=counter; //向緩衝區寫入數據
else
buffer2[i]=counter;
counter++; //模擬數據採集卡產生數據
msleep(20); //每50ms產生一個數
}
bufNo++;//緩衝區序號
if (curBuf==1) // 切換當前寫入緩衝區
curBuf=2;
else
curBuf=1;
fullBufs.release(); //有了一個滿的緩衝區,available==1
}
quit();
}
void QThreadShow::run()
{
m_stop=false;//啓動線程時令m_stop=false
int n=fullBufs.available();
if (n>0)
fullBufs.acquire(n); //將fullBufs可用資源個數初始化爲0
while(!m_stop)//循環主體
{
fullBufs.acquire(); //等待有緩衝區滿,當fullBufs.available==0阻塞
int bufferData[BufferSize];
int seq=bufNo;
if(curBuf==1) //當前在寫入的緩衝區是1,那麼滿的緩衝區是2
for (int i=0;i<BufferSize;i++)
bufferData[i]=buffer2[i]; //快速拷貝緩衝區數據
else
for (int i=0;i<BufferSize;i++)
bufferData[i]=buffer1[i];
emptyBufs.release();//釋放一個空緩衝區
emit newValue(bufferData,BufferSize,seq);//給主線程傳遞數據
}
quit();
}
QThreadShow::QThreadShow()
{
}
void QThreadShow::stopThread()
{
// QMutexLocker locker(&mutex);
m_stop=true;
}
在共享變量區定義了兩個緩衝區 buffer1 和 buffer2, 都是長度爲 BufferSize 的數組。變量 curBuf 記錄當前寫入操作 的緩衝區編號,其值只能是 1或 2 ,表示 buffer1 或 buffer2, bufNo 是累積的緩衝區個數編號,counter 是模擬採集數據的變量 。
信號量 emptyBufs 初始資源個數爲 2 ,表示有 2 個空的緩衝區可用。
信號量 如llBufs 初始化資源個數爲 0 , 表示寫滿數據的緩衝區個數爲零。
QThreadDAQ::run ()採用雙緩衝方式進行模擬數據採集 ,線程啓動時初始化共享變量 ,特別的是使 emptyBufs 的可用 資源個數初始化爲 2 。
在 while 循環體裏,第一行語句 emptyBufs.acquire()使信號量 emptyBufs 獲取一個資源 , 即獲取一個空的緩衝區。用於數據緩存的有兩個緩衝區,只要有一個空的緩衝區 ,就可以向這個緩衝區寫入數據。
while 循環體裏的 for 循環每隔 50 毫秒使 counter 值加 1 , 然後寫入當前正在寫入的緩衝區 ,當前寫入哪個緩衝 區由 curBuf 決定。 counter 是模擬採集的數據,連續增加可以判斷採集的數據是否連續。
完成 for 循環後正好寫滿一個緩衝區,這時改變 curBuf 的值,切換用於寫入的緩衝區 。
寫滿一個緩衝區之後,使用 “fullBufs.release()爲信號量 fullBufs 釋放一個資源,這時 fullBufs.available= 1,表示有一個緩衝區被寫滿了。 這樣, QThreadShow 線程裏使用fullBufs.acquire()就可以獲得一個資源 , 可以讀取己寫滿的緩衝區裏的數據 。
QThreadShow: : run ()用於監測是否有已經寫滿數據的緩衝區,只要有緩衝區寫滿了數據,就立刻讀取出數據,然後釋放這個緩衝區給 QThreadDAQ 線程用於寫入。
QThreadShow: :run () 函數的初始化部分使 fullBufs . available == 0 ,即 線程剛啓動時是沒有資源的 。
在 while 循環體裏第一行語句就是通過fullBufs.acquire()以 阻塞方式獲取一個資源 ,只有當QThreadDAQ 線程裏寫滿一個緩衝區,執行一 次 fullBu s . release()後, fullBufs.acquire()才獲得資源並執行後面的代碼。後面的代碼就立即用臨時變量將緩衝區裏的數據讀取出來 , 再調用emptyBufs. release()給信號量 emptyBufs 釋放一個資源,然後發射信號 newValue , 由主線程讀取數據井顯示。
所以,這裏使用了雙緩衝區、兩個信號量實現採集和讀取兩個線程的協調操作 。採集線程裏使用 emptyBufs.acquire()獲取可以寫入的緩衝區。
實際使用數據來集卡進行連續數據採集時, 採集線程是不能停頓下來的 ,也就是說萬一讀取線程執行較慢,採集線程是不會等待的 。 所以實際情況下,讀取線程的操作應該比採集線程快 。
主頁面的頭文件:
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QTimer>
#include "qmythread.h"
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
private:
QThreadDAQ threadProducer;
QThreadShow threadConsumer;
protected:
void closeEvent(QCloseEvent *event);
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private slots:
void onthreadA_started();
void onthreadA_finished();
void onthreadB_started();
void onthreadB_finished();
void onthreadB_newValue(int *data, int count, int bufNo);
void on_btnClear_clicked();
void on_btnStopThread_clicked();
void on_btnStartThread_clicked();
private:
Ui::Dialog *ui;
};
#endif // DIALOG_H
對應的cpp文件:
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QTimer>
#include "qmythread.h"
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
private:
QThreadDAQ threadProducer;
QThreadShow threadConsumer;
protected:
void closeEvent(QCloseEvent *event);
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private slots:
void onthreadA_started();
void onthreadA_finished();
void onthreadB_started();
void onthreadB_finished();
void onthreadB_newValue(int *data, int count, int bufNo);
void on_btnClear_clicked();
void on_btnStopThread_clicked();
void on_btnStartThread_clicked();
private:
Ui::Dialog *ui;
};
#endif // DIALOG_H
在實際的數據採集中 , 要保證不丟失緩衝區或數據點 ,數據讀取線程的速度必須快過數據寫入緩衝區 的線程的速度。
歡迎大家點贊,收藏,一起加油吧。