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

      在实际的数据采集中 , 要保证不丢失缓冲区或数据点 ,数据读取线程的速度必须快过数据写入缓冲区 的线程的速度。
 

欢迎大家点赞,收藏,一起加油吧。

发布了40 篇原创文章 · 获赞 13 · 访问量 5883

猜你喜欢

转载自blog.csdn.net/weixin_42126427/article/details/104822694