信号量( 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
在实际的数据采集中 , 要保证不丢失缓冲区或数据点 ,数据读取线程的速度必须快过数据写入缓冲区 的线程的速度。
欢迎大家点赞,收藏,一起加油吧。