沈阳做一个网站需要多少钱,wordpress怎么弄在线表单,网站机房建设流程,外贸企业网站推广方案文章目录 1. 信号量的原理2. 双缓冲区数据采集和读取线程类设计3. QThreadDAQ和QThreadShow 的使用4. 源码4.1 可视化UI设计框架4.2 qmythread.h4.3 qmythread.cpp4.4 dialog.h4.5 dialog.cpp 1. 信号量的原理
信号量(Semaphore)是另一种限制对共享资源进行访问的线程同步机制… 文章目录 1. 信号量的原理2. 双缓冲区数据采集和读取线程类设计3. QThreadDAQ和QThreadShow 的使用4. 源码4.1 可视化UI设计框架4.2 qmythread.h4.3 qmythread.cpp4.4 dialog.h4.5 dialog.cpp 1. 信号量的原理
信号量(Semaphore)是另一种限制对共享资源进行访问的线程同步机制它与互斥量(Mutex)相似但是有区别。一个互斥量只能被锁定一次而信号量可以多次使用。信号量通常用来保护一定数量的相同的资源如数据采集时的双缓冲区。
QSemaphore 是实现信号量功能的类它提供以下几个基本的函数:
acquire(int n)尝试获得 n 个资源。如果没有这么多资源线程将阻塞直到有 n 个资源可用release(int n)释放 n 个资源如果信号量的资源已全部可用之后再 release()就可以创建更多的资源增加可用资源的个数:int available()返回当前信号量可用的资源个数这个数永远不可能为负数如果为 0就说明当前没有资源可用;bool tryAcquire(int n 1)尝试获取 n 个资源不成功时不阻塞线程。
定义QSemaphore 的实例时可以传递一个数值作为初始可用的资源个数。
下面的一段示意代码说明 QSemaphore 的几个函数的作用。
QSemaphore WC(5); // WC.available() 5初始资源个数为 5个
WC.acquire(4): // WC.available() 1用了4 个资源还剩余1个可用
WC.release(2); // WC.available() 3释放了2个资源剩余3个可用
WC.acquire(3); // WC.available() 0又用了3 个资源剩余0个可用
WC.tryAcquire(1); //因为WC.available() 0返回 false
WC.acquire(); //因为 wc.available() 0没有资源可用阻塞为了理解信号量及上面这段代码的意义可以假想变量 WC 是一个公共卫生间初始化时定义WC有5个位置可用。 WC.acquire(4)成功进去 4 个人占用了4 个位置还剩余1个位置 WC.release(2)出来了 2个人剩余3 个位置可用: WC.acquire(3)又进去 3 个人剩余0个位置可用; WC.tryAcquire(1),有一个人尝试进去但是因为没有位置了他不等待走了,tryAcquire()函数返回 false: WC.acquire()有一个人必须进去但是因为没有位置了他就一直在外面等着直到有其他人出来空余出位置来。
互斥量相当于列车上的卫生间一次只允许一个人进出信号量则是多人公共卫生间允许多人进出。n 个资源就是信号量需要保护的共享资源至于资源如何分配就是内部处理的问题了。
2. 双缓冲区数据采集和读取线程类设计
理解可以用于实现自行定义的缓冲区大小利用2个子线程对不断产生的数据不间断进行写入及处理主线程主要进行显示
信号量通常用来保护一定数量的相同的资源如数据采集时的双缓冲区适用于Producer/Consumer 模型。
在实例 samp13_5中创建类似于 Producer/Consumer 模型的两个线程类 QThreadDAQ 和QThreadShow。qmythread.h 文件中这两个类的定义如下:
#ifndef QMYTHREAD_H
#define QMYTHREAD_H//#include QObject
#include QThreadclass QThreadDAQ : public QThread
{Q_OBJECTprivate:bool m_stopfalse; //停止线程
protected:void run() Q_DECL_OVERRIDE;
public:QThreadDAQ();void stopThread();
};class QThreadShow : public QThread
{Q_OBJECT
private:bool m_stopfalse; //停止线程
protected:void run() Q_DECL_OVERRIDE;
public:QThreadShow();void stopThread();
signals:void newValue(int *data,int count, int seq);
};
#endif // QMYTHREAD_H
QThreadDAQ 是数据采集线程例如在使用数据采集卡进行连续数据采集时需要一个单独的线程将采集卡采集的数据读取到缓冲区内。 QThreadShow 是数据读取线程,用于读取已存满数据的缓冲区中的数据并传递给主线程显示,采用信号与槽机制与主线程交互。 QThreadDAQ/QThreadShow 类的定义与使用 QWaitCondition 的实例 samp13_4中的QThreadProducer/QThreadConsumer 类的定义类似只是QThreadShow 的信号 newValue()采用了指针作为传递参数用于一次传递出一个缓冲区的数据。
qmythread.cpp 文件中QThreadDAQ和QThreadShow 的主要功能代码如下:
#include qmythread.h
#include QSemaphoreconst int BufferSize 8;
int buffer1[BufferSize];
int buffer2[BufferSize];
int curBuf1; //当前正在写入的Bufferint bufNo0; //采集的缓冲区序号quint8 counter0;//数据生成器QSemaphore emptyBufs(2);//信号量空的缓冲区个数初始资源个数为2
QSemaphore fullBufs; //满的缓冲区个数初始资源为0QThreadDAQ::QThreadDAQ()
{}void QThreadDAQ::stopThread()
{m_stoptrue;
}void QThreadDAQ::run()
{m_stopfalse;//启动线程时令m_stopfalsebufNo0;//缓冲区序号curBuf1; //当前写入使用的缓冲区counter0;//数据生成器int nemptyBufs.available();if (n2) //保证 线程启动时emptyBufs.available2emptyBufs.release(2-n);while(!m_stop)//循环主体{emptyBufs.acquire();//获取一个空的缓冲区for(int i0;iBufferSize;i) //产生一个缓冲区的数据{if (curBuf1)buffer1[i]counter; //向缓冲区写入数据elsebuffer2[i]counter;counter; //模拟数据采集卡产生数据msleep(50); //每50ms产生一个数}bufNo;//缓冲区序号if (curBuf1) // 切换当前写入缓冲区curBuf2;elsecurBuf1;fullBufs.release(); //有了一个满的缓冲区,available1}quit();
}void QThreadShow::run()
{m_stopfalse;//启动线程时令m_stopfalseint nfullBufs.available();if (n0)fullBufs.acquire(n); //将fullBufs可用资源个数初始化为0while(!m_stop)//循环主体{fullBufs.acquire(); //等待有缓冲区满,当fullBufs.available0阻塞int bufferData[BufferSize];int seqbufNo;if(curBuf1) //当前在写入的缓冲区是1那么满的缓冲区是2for (int i0;iBufferSize;i)bufferData[i]buffer2[i]; //快速拷贝缓冲区数据elsefor (int i0;iBufferSize;i)bufferData[i]buffer1[i];emptyBufs.release();//释放一个空缓冲区emit newValue(bufferData,BufferSize,seq);//给主线程传递数据}quit();
}QThreadShow::QThreadShow()
{}void QThreadShow::stopThread()
{m_stoptrue;
}在共享变量区定义了两个缓冲区 buffer1和 buffer2都是长度为 BufferSize 的数组。
变量 curBuf 记录当前写入操作的缓冲区编号其值只能是 1或2表示 bufferl 或 buffer2bufNo是累积的缓冲区个数编号counter 是模拟采集数据的变量。 信号量emptyBufs 初始资源个数为2表示有2个空的缓冲区可用。
信号量 fullBufs初始化资源个数为0表示写满数据的缓冲区个数为零。
QThreadDAQ::run()采用双缓冲方式进行模拟数据采集线程启动时初始化共享变量特别的是使emptyBufs 的可用资源个数初始化为2。 在while 循环体里第一行语句 emptyBufs.acquire()使信号量emptyBufs 获取一个资源即获取一个空的缓冲区。用于数据缓存的有两个缓冲区只要有一个空的缓冲区就可以向这个缓冲区写入数据。
while 循环体里的 for 循环每隔 50 毫秒使 counter 值加 1然后写入当前正在写入的缓冲区当前写入哪个缓冲区由 curBuf 决定。counter 是模拟采集的数据连续增加可以判断采集的数据是否连续。
完成 for 循环后正好写满一个缓冲区这时改变 curBuf 的值切换用于写入的缓冲区。
写满一个缓冲区之后使用 fullBufs.release()为信号量 fullBufs 释放一个资源这时 fullBufs.availablel表示有一个缓冲区被写满了。这样QThreadShow 线程里使用 fullBufs.acquire()就可以获得一个资源可以读取已写满的缓冲区里的数据。
QThreadShow::run()用于监测是否有已经写满数据的缓冲区只要有缓冲区写满了数据就立刻读取出数据然后释放这个缓冲区给 OThreadDAQ 线程用于写入。
QThreadShow::run()函数的初始化部分使 fullBufs.available0即线程刚启动时是没有资源的。
在 while循环体里第一行语句就是通过 fullBufs.acquire()以阻塞方式获取一个资源只有当QThreadDAQ 线程里写满一个缓冲区执行一次fullBufs.release()后fullBufs.acquire()才获得资源并执行后面的代码。后面的代码就立即用临时变量将缓冲区里的数据读取出来再调用emptyBufs.release()给信号量emptyBufs 释放一个资源然后发射信号 newValue由主线程读取数据并显示。 所以这里使用了双缓冲区、两个信号量实现采集和读取两个线程的协调操作。采集线程里使用emptyBufs.acquire()获取可以写入的缓冲区。 实际使用数据采集卡进行连续数据采集时采集线程是不能停顿下来的也就是说万一读取线程执行较慢采集线程是不会等待的。所以实际情况下读取线程的操作应该比采集线程快。
3. QThreadDAQ和QThreadShow 的使用
设计窗口基于 QDialog 应用程序 samp13_5对话框的类定义如下(省略了一些不重要的或与前面实例重复的部分内容):
class Dialog : public QDialog
{Q_OBJECTprivate:QThreadDAQ threadProducer;QThreadShow threadConsumer;
private slots:void onthreadB_newValue(int *data, int count, int bufNo);};Dialog类定义了两个线程的实例threadProducer 和 threadConsumer。
自定义了一个槽函数 onthreadB_newValue()用于与 threadConsumer 的信号关联在 Dialog的构造函数里进行了关联。
connect(threadConsumer,SIGNAL(newValue(int*,int,int)),this,SLOT(onthreadB_newValue(int*,int,int)));
槽函数onthreadB_newValue()的功能就是读取一个缓冲区里的数据并显示其实现代码如下
void Dialog::onthreadB_newValue(int *data, int count, int bufNo)
{ //读取threadConsumer 传递的缓冲区的数据QString strQString::asprintf(第 %d 个缓冲区,bufNo);for (int i0;icount;i){strstrQString::asprintf(%d, ,*data);data;}strstr\n;ui-plainTextEdit-appendPlainText(str);
}传递的指针型参数int*data 是一个数组指针count 是缓冲区长度。此处注意主线程和子线程利用信号槽传递数组值的方法
“启动线程”和“结束线程”两个按钮的代码如下(省略了按键使能控制的代码):
void Dialog::on_btnStopThread_clicked()
{//结束线程
// threadConsumer.stopThread();//结束线程的run()函数执行threadConsumer.terminate(); //因为threadB可能处于等待状态所以用terminate强制结束threadConsumer.wait();//threadProducer.terminate();//结束线程的run()函数执行threadProducer.wait();//ui-btnStartThread-setEnabled(true);ui-btnStopThread-setEnabled(false);
}void Dialog::on_btnStartThread_clicked()
{//启动线程threadConsumer.start();threadProducer.start();ui-btnStartThread-setEnabled(false);ui-btnStopThread-setEnabled(true);
}启动线程时先启动 threadConsumer再启动 threadProducer否则可能丢失第1个缓冲区的数据。 结束线程时都采用 terminate()函数强制结束线程因为两个线程之间有互锁的关系若不使用terminate()强制结束会出现线程无法结束的问题。
程序运行时的界面如图 13-3 所示 从图 13-3 可以看出没有出现丢失缓冲区或数据点的情况两个线程之间协调的很好将QThreadDAQ:run()函数中模拟采样率的延时时间调整为2秒也没问题(正常设置为50毫秒)。
在实际的数据采集中要保证不丢失缓冲区或数据点数据读取线程的速度必须快过数据写入缓冲区的线程的速度。
4. 源码
4.1 可视化UI设计框架 4.2 qmythread.h
#ifndef QMYTHREAD_H
#define QMYTHREAD_H//#include QObject
#include QThreadclass QThreadDAQ : public QThread
{Q_OBJECTprivate:bool m_stopfalse; //停止线程
protected:void run() Q_DECL_OVERRIDE;
public:QThreadDAQ();void stopThread();
};class QThreadShow : public QThread
{Q_OBJECT
private:bool m_stopfalse; //停止线程
protected:void run() Q_DECL_OVERRIDE;
public:QThreadShow();void stopThread();
signals:void newValue(int *data,int count, int seq);
};
#endif // QMYTHREAD_H
4.3 qmythread.cpp
#include qmythread.h
#include QSemaphoreconst int BufferSize 8;
int buffer1[BufferSize];
int buffer2[BufferSize];
int curBuf1; //当前正在写入的Bufferint bufNo0; //采集的缓冲区序号quint8 counter0;//数据生成器QSemaphore emptyBufs(2);//信号量空的缓冲区个数初始资源个数为2
QSemaphore fullBufs; //满的缓冲区个数初始资源为0QThreadDAQ::QThreadDAQ()
{}void QThreadDAQ::stopThread()
{m_stoptrue;
}void QThreadDAQ::run()
{m_stopfalse;//启动线程时令m_stopfalsebufNo0;//缓冲区序号curBuf1; //当前写入使用的缓冲区counter0;//数据生成器int nemptyBufs.available();if (n2) //保证 线程启动时emptyBufs.available2emptyBufs.release(2-n);while(!m_stop)//循环主体{emptyBufs.acquire();//获取一个空的缓冲区for(int i0;iBufferSize;i) //产生一个缓冲区的数据{if (curBuf1)buffer1[i]counter; //向缓冲区写入数据elsebuffer2[i]counter;counter; //模拟数据采集卡产生数据msleep(50); //每50ms产生一个数}bufNo;//缓冲区序号if (curBuf1) // 切换当前写入缓冲区curBuf2;elsecurBuf1;fullBufs.release(); //有了一个满的缓冲区,available1}quit();
}void QThreadShow::run()
{m_stopfalse;//启动线程时令m_stopfalseint nfullBufs.available();if (n0)fullBufs.acquire(n); //将fullBufs可用资源个数初始化为0while(!m_stop)//循环主体{fullBufs.acquire(); //等待有缓冲区满,当fullBufs.available0阻塞int bufferData[BufferSize];int seqbufNo;if(curBuf1) //当前在写入的缓冲区是1那么满的缓冲区是2for (int i0;iBufferSize;i)bufferData[i]buffer2[i]; //快速拷贝缓冲区数据elsefor (int i0;iBufferSize;i)bufferData[i]buffer1[i];emptyBufs.release();//释放一个空缓冲区emit newValue(bufferData,BufferSize,seq);//给主线程传递数据}quit();
}QThreadShow::QThreadShow()
{}void QThreadShow::stopThread()
{m_stoptrue;
}
4.4 dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include QDialog
#include QTimer#include qmythread.hnamespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTprivate: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
4.5 dialog.cpp
#include dialog.h
#include ui_dialog.hvoid Dialog::closeEvent(QCloseEvent *event)
{//窗口关闭if (threadProducer.isRunning()){threadProducer.terminate();//结束线程的run()函数执行threadProducer.wait();//}if (threadConsumer.isRunning()){threadConsumer.terminate(); //因为threadB可能处于等待状态所以用terminate强制结束threadConsumer.wait();//}event-accept();
}Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog)
{ui-setupUi(this);connect(threadProducer,SIGNAL(started()),this,SLOT(onthreadA_started()));connect(threadProducer,SIGNAL(finished()),this,SLOT(onthreadA_finished()));connect(threadConsumer,SIGNAL(started()),this,SLOT(onthreadB_started()));connect(threadConsumer,SIGNAL(finished()),this,SLOT(onthreadB_finished()));connect(threadConsumer,SIGNAL(newValue(int*,int,int)),this,SLOT(onthreadB_newValue(int*,int,int)));
}Dialog::~Dialog()
{delete ui;
}void Dialog::onthreadA_started()
{ui-LabA-setText(Thread Producer状态: started);
}void Dialog::onthreadA_finished()
{ui-LabA-setText(Thread Producer状态: finished);
}void Dialog::onthreadB_started()
{ui-LabB-setText(Thread Consumer状态: started);
}void Dialog::onthreadB_finished()
{ui-LabB-setText(Thread Consumer状态: finished);
}void Dialog::onthreadB_newValue(int *data, int count, int bufNo)
{ //读取threadConsumer 传递的缓冲区的数据QString strQString::asprintf(第 %d 个缓冲区,bufNo);for (int i0;icount;i){strstrQString::asprintf(%d, ,*data);data;}strstr\n;ui-plainTextEdit-appendPlainText(str);
}void Dialog::on_btnClear_clicked()
{ui-plainTextEdit-clear();
}void Dialog::on_btnStopThread_clicked()
{//结束线程
// threadConsumer.stopThread();//结束线程的run()函数执行threadConsumer.terminate(); //因为threadB可能处于等待状态所以用terminate强制结束threadConsumer.wait();//threadProducer.terminate();//结束线程的run()函数执行threadProducer.wait();//ui-btnStartThread-setEnabled(true);ui-btnStopThread-setEnabled(false);
}void Dialog::on_btnStartThread_clicked()
{//启动线程threadConsumer.start();threadProducer.start();ui-btnStartThread-setEnabled(false);ui-btnStopThread-setEnabled(true);
}