Qt 信號量的線程同步

       信號量( 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

      在實際的數據採集中 , 要保證不丟失緩衝區或數據點 ,數據讀取線程的速度必須快過數據寫入緩衝區 的線程的速度。
 

歡迎大家點贊,收藏,一起加油吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章