QT6之多线程控制——互斥量和信号量

news/2024/10/23 8:33:10/

在程序中,通常竞争使用临界资源,但如果不加限制就很可能出现异常或未达到预期的结果。

临界资源一次仅允许被一个线程使用,它可以是一块内存、一个数据结构、一个文件或者任何其他具有排他性使用的东西。

这些必须互斥执行的代码段称为“临界区(Critical Section,CS)”。临界区(代码段)实施对临界资源的操作,为了阻止问题的产生,一次只能有一个线程进入临界区。

一、互斥量

互斥量是通过QMutexQMutexLocker实现。

QMutex 的目的是保护一个对象、数据结构或代码段,以便一次只有一个线程可以访问它(这类似于 Java 关键字synchronized)。通常最好将互斥锁与QMutexLocker一起使用,因为这样可以轻松确保锁定和解锁的执行一致。

QMutex构造的锁,默认是QMutex::NonRecursive,也就是说只能锁定一次,如果无法确定时,可以用QMutex::try_lock()得到目前锁的状态;

QMutex::QMutex(RecursionMode mode = NonRecursive)

如下演示锁和临界资源直接的影响: 

main.cpp

#include <QCoreApplication>
#include "mythread.h"
#include <QDebug>int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// creating three thread instancesMyThread thread1("A"), thread2("B"), thread3("C");qDebug() << "hello from GUI thread " << a.thread()->currentThreadId();// thread start -> call run()thread1.start();thread2.start();thread3.start();return a.exec();
}

mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <QString>class MyThread : public QThread
{
public:// constructor// set name and Stop is set as false by defaultMyThread(QString s, bool b = false);// overriding the QThread's run() methodvoid run();// variable that mutex protectsbool Stop;
private:QString name;
};#endif // MYTHREAD_H

mythread.cpp

#include "mythread.h"
#include <QDebug>
#include <QMutex>MyThread::MyThread(QString s, bool b) : name(s), Stop(b
{
}// run() will be called when a thread starts
void MyThread::run()
{qDebug() << this->name << " " << this->Stop;for(int i = 0; i <= 5; i++){QMutex mutex;// prevent other threads from changing the "Stop" valuemutex.lock();if(this->Stop) break;mutex.unlock();qDebug() << this->name << " " << i;}
}

 QMutexLocker可以简化互斥量的处理,自动lock和unlock,可以避免复杂情况时出错。

如下所示:在mythread.h增加一个 QMutex mutex;然后mythread.cpp就如下所示了,通常仅使用一条语句叫解决了复杂的lock和unlock问题。


void MyThread::run()
{qDebug() << this->name << " " << this->Stop;for(int i = 0; i <= 5; i++){QMutexLocker locker(&mutex);if(this->Stop) break;qDebug() << this->name << " " << i;}
}

二、信号量

信号量可以理解为对互斥量功能的扩展,互斥量只能锁定一次而信号量可以获取多次,它可以用来保护一定数量的同种资源。

信号量的典型用法是控制生产者/消费者之间共享的环形缓冲区。

 qmythread.cpp

#ifndef QMYTHREAD_H
#define QMYTHREAD_H//#include    <QObject>
#include    <QThread>
//#include    <QMutex>class QThreadDAQ : public QThread
{Q_OBJECTprivate: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.h 

#include    "qmythread.h"
#include    <QSemaphore>
//#include    <QTime>const int BufferSize = 8;
int buffer1[BufferSize];
int buffer2[BufferSize];
int curBuf=1; //当前正在写入的Bufferint bufNo=0; //采集的缓冲区序号quint8   counter=0;//数据生成器QSemaphore emptyBufs(2);//信号量:空的缓冲区个数,初始资源个数为2
QSemaphore fullBufs; //满的缓冲区个数,初始资源为0QThreadDAQ::QThreadDAQ()
{}void QThreadDAQ::stopThread()
{
//    QMutexLocker  locker(&mutex);m_stop=true;
}void QThreadDAQ::run()
{m_stop=false;//启动线程时令m_stop=falsebufNo=0;//缓冲区序号curBuf=1; //当前写入使用的缓冲区counter=0;//数据生成器int n=emptyBufs.available();if (n<2)  //保证 线程启动时emptyBufs.available==2emptyBufs.release(2-n);while(!m_stop)//循环主体{emptyBufs.acquire();//获取一个空的缓冲区for(int i=0;i<BufferSize;i++) //产生一个缓冲区的数据{if (curBuf==1)buffer1[i]=counter; //向缓冲区写入数据elsebuffer2[i]=counter;counter++; //模拟数据采集卡产生数据msleep(20); //每50ms产生一个数}bufNo++;//缓冲区序号if (curBuf==1) // 切换当前写入缓冲区curBuf=2;elsecurBuf=1;fullBufs.release(); //有了一个满的缓冲区,available==1}quit();
}void QThreadShow::run()
{m_stop=false;//启动线程时令m_stop=falseint n=fullBufs.available();if (n>0)fullBufs.acquire(n); //将fullBufs可用资源个数初始化为0while(!m_stop)//循环主体{fullBufs.acquire(); //等待有缓冲区满,当fullBufs.available==0阻塞int bufferData[BufferSize];int seq=bufNo;if(curBuf==1) //当前在写入的缓冲区是1,那么满的缓冲区是2for (int i=0;i<BufferSize;i++)bufferData[i]=buffer2[i]; //快速拷贝缓冲区数据elsefor (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;
}

 dialog.h

#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include    <QTimer>#include    "qmythread.h"namespace 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

 dialog.cpp

#include "dialog.h"
#include "ui_dialog.h"void 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  str=QString::asprintf("第 %d 个缓冲区:",bufNo);for (int i=0;i<count;i++){str=str+QString::asprintf("%d, ",*data);data++;}str=str+'\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);
}

 

 如图可以看出,没有出现丢失缓冲区或数据点的情况,两个线程之间协调的很好,将run函数中模拟采样率的延时时间调整为2毫秒也没问题(正常设置为 50 毫秒)在实际的数据采集中,要保证不丢失缓冲区或数据点,数据读取线程的速度必须快过数据写入缓冲区的线程的速度。

 //信号量源代码已经上传为压缩包,可在文章顶部下载;


http://www.ppmy.cn/news/574857.html

相关文章

神舟战神Z8-CU7NA折腾Windows10 + Manjaro双系统

前言: 此博文是笔者折腾笔记, 同时贡献给爱折腾的Linuxer, 笔者使用过Ubuntu, 红帽RHEL, CentOS, Fedora, Manjaro(按时间先后顺序), 我个人觉得好用性一个比一个强. 另外①操作系统而言看需求和个人喜好, 用Windows还是Linux或者MacOS没有什么好争辩的, ②Linuxer用图形操作还…

神舟测试软件,神舟战神P5常规性能软件测试_神舟 战神P5-i78172D1_笔记本评测-中关村在线...

■常规软件测试 ◆CPU-Z&#xff1a; CPU-Z是一款家喻户晓的CPU检测软件&#xff0c;除了使用Intel或AMD自己的检测软件之外&#xff0c;我们平时使用最多的此类软件就数它了。CPU-Z提供一些关於处理器的资讯&#xff0c;包含了制造厂及处理器名称&#xff0c;核心构造及封装技…

在React+ts中集成高德地图(保姆级教程)

前往高德地图开发平台高德开放平台 | 高德地图API 一&#xff1a;申请高德key 去高德官网去创建一个属于自己的地图应用 &#xff08;得到key和秘钥&#xff09; 。 首先&#xff0c;我们要注册一个开发者账号&#xff0c;根据实际情况填写&#xff0c;身份写个人&#xff1a;…

体验移动100M宽带

移动免费送100M宽带&#xff0c;光猫是中移物联公司的GM620&#xff0c;默认是光猫拨号&#xff0c;一个千兆口&#xff0c;带2.4和5G无线功能&#xff0c;可是WIFI信号很弱&#xff0c;还是关了无线用另一个无线路由器做AP。 测速很快&#xff0c;上下行都是100M&#xff0c;…

无线移动通信基础知识

目录 一、无线移动通信的特点二、无线移动通信的电波传播机制及其应用三、移动信道三大损耗3.1 路径损耗3.2 快衰落损耗3.3 慢衰落损耗 四、移动信道四大效应4.1 阴影效应4.2 多径效应4.3 多普勒效应4. 远近效应 五、大尺度衰落与小尺度衰落5.1 大尺度衰落5.2 小尺度衰落 六、相…

无线/移动通信网络基本概念整理

http://wjf88223.blog.163.com/blog/static/35168001201142210165865/ FDMA (Frequency Division Multiple Access)频分多址 频分多址是把分配给无线蜂窝电话通讯的频段分为30个信道&#xff0c;每一个信道都能够传输语音通话、数字服务和数字数据。频分多址是模拟高级移动电…

移动光猫怎么设置虚拟服务器设置,移动光纤怎么设置无线路由器?

在本文中,将给大家详细的介绍,移动光纤宽带设置路由器上网的方法。 为了照顾0基础的新手用户,下面鸿哥会尽量用通俗易懂的语言,来介绍路由器的设置;如果你对路由器、电脑比较熟悉了,不要觉得啰嗦。 温馨提示: 如果你的路由器已经设置过了,请把它恢复出厂设置,然后按照…

移动宽带之优化

移动宽带&#xff0c;价格便宜&#xff0c;印象中都认为速度慢。 这边原先使用10Mb联通光纤&#xff0c;上行却只有可怜的0.5Mb。 这次更换了移动的100Mb&#xff0c;上行有5&#xff5e;6Mb样子。 路由是使用Panabit标准版。 开始是新浪视频&#xff0c;邮箱慢&#xff0c;打…