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

news2024/11/15 12:08:26

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

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

这些必须互斥执行的代码段称为“临界区(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 instances
    MyThread 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 default
    MyThread(QString s, bool b = false);

    // overriding the QThread's run() method
    void run();

    // variable that mutex protects
    bool 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" value
        mutex.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_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.h 

#include    "qmythread.h"
#include    <QSemaphore>
//#include    <QTime>

const int BufferSize = 8;
int buffer1[BufferSize];
int buffer2[BufferSize];
int curBuf=1; //当前正在写入的Buffer

int bufNo=0; //采集的缓冲区序号

quint8   counter=0;//数据生成器

QSemaphore emptyBufs(2);//信号量:空的缓冲区个数,初始资源个数为2
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;
}

 dialog.h

#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

 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.coloradmin.cn/o/684233.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用VmWare安装黑苹果系统

目录 1.介绍2.破解安装VMware3.unlocker解锁虚拟机3.1 关闭VMware相关的进程3.2 执行安装命令 4.VmWare创建虚拟机5. 下载并配置镜像以及虚拟机设置5.1 修改镜像5.2 修改虚拟机安装路径文件内容 6. 选择镜像启动虚拟机7.安装macOS系统7.1 开启此虚拟机7.2 选择语言->简体中文…

TEC半导体热电温控技术在复合相变材料储热性能测试中的应用

摘要&#xff1a;针对定形相变复合材料热性能测试中ASTM C1784动态热流计法和ASTM C518稳态热流计法的高精度可编程快速温度控制问题&#xff0c;本文提出了采用单独两路TEC半导体热电加热制冷模组作为执行机构的解决方案。解决方案中还配备了不同加热功率的TEC控制电源模块、高…

Netty 聊天室项目案例

1. 登入 在连接建立好之后&#xff0c;客户端发送登入&#xff0c;将登入消息封装为LoginRequestMessage这个类的对象&#xff0c; ctx.writeAndFlush(loginRequestMessage);&#xff09;使用ctx发送&#xff0c;注意入站处理器调用写相关方法&#xff0c;会触发出站处理器&am…

金升阳|过压保护是什么意思?过压保护电路的构建

过压保护是指在电路中加入一种保护措施&#xff0c;以避免电路过压而导致器件损坏、安全事故等情况的发生。在实际工程中&#xff0c;过压保护电路通常由过压检测电路和过压保护器件组成。本文将详细介绍过压保护的原理、过压保护电路的构建方法、常见的过压保护器件以及应用实…

【LLMs 入门实战 】第二式:MiniGPT4 模型学习与实战

2023年4月17日&#xff0c;多模态问答模型MiniGPT-4发布&#xff0c;实现了GPT-4里的宣传效果《MiniGPT-4: Enhancing Vision-language Understanding with Advanced Large Language Models》《MiniGPT-4&#xff1a;使用高级大语言模型增强视觉语言理解》 模型介绍模型架构微调…

如何在conda环境中正确地使用pip

导言&#xff1a; 在conda环境下使用pip安装库时&#xff0c;许多时候会出现一些奇怪的现象&#xff0c;即用pip安装完成后在该conda环境下却没有该库。本文将说明该问题出现的原因&#xff0c;修复方式和如何正确地在conda环境中使用pip。 问题现象 复现&#xff1a; 新建环…

1-html

一 HTML 初体验 1 HTML 定义 HTML 超文本标记语言——HyperText Markup Language。 超文本是什么&#xff1f;链接标记是什么&#xff1f; 标记也叫标签&#xff0c;带尖括号的文本 2 标签语法 标签成对出现&#xff0c;中间包裹内容<>里面放英文字母&#xff08;标签…

Vim 自定义配色

本文首发于我的个人博客&#xff0c;欢迎点击访问&#xff0c;无广告节面简洁&#xff01; 最近重新开始学习Vim装上了WSL2&#xff0c;但发现Windows Terminal和vim的组合还是有很多问题需要解决的&#xff0c;由其默认的配色在某些状态下根本看不清字体&#xff0c;所以折腾…

小满vue3笔记(含源码解读)

第一章 1.mvvm架构 2.回顾vue2对比vue3 区别&#xff1a; vue2选项式api vue3组合式api 关于这两个的区别&#xff0c;你可以不准确的理解为&#xff0c;选项式api更贴近原生标准html文件结构&#xff1b; 而组合式api就像在html标签中写css&#xff1b;当然做了优化&…

最新大麦网抢票脚本-Python实战

学习时候的一个小例子&#xff0c;python挺有趣的&#xff0c;希望技术可以更进步 我也不多说啥了直接上图 系统:win10 python 版本:3.8.10 需要安装的库:selenium 安装方法: pip install selenium 抢H5版本也写了&#xff0c;但是速度有点慢2…5 就不发了 不如用autojs写感…

前端vue入门(纯代码)15

【16.Vue的过渡与动画】 1.点击切换按钮&#xff1a;实现某一元素的显示/隐藏 Test.vue文件中 <template><div><!-- 点击事件触发后&#xff0c;isShow取反 --><!-- 通过点击按钮让h1标签出现或者消失 --><button click"isShow !isShow&qu…

哈工大计算机网络课程网络层协议详解之:DHCP协议

哈工大计算机网络课程网络层协议详解之&#xff1a;DHCP协议 文章目录 哈工大计算机网络课程网络层协议详解之&#xff1a;DHCP协议如何获得IP地址&#xff1f;硬编码动态主机配置协议-DHCP&#xff1a;&#xff08;Dynamic Host Configuration Protocol&#xff09; 动态主机配…

设计模式篇(Java):前言(UML类图、七大原则)

编写软件过程中&#xff0c;程序员面临着来自耦合性&#xff0c;内聚性以及可维护性&#xff0c;可扩展性&#xff0c;重用性&#xff0c;灵活性等多方面的挑战&#xff0c;设计模式是为了让程序(软件)&#xff0c;具有更好&#xff1a; 代码重用性 (即&#xff1a;相同功能的…

Web自动化测试平台的设计与落地

目录 前言 一、目标和定位 二、平台特点 三、系统架构 四、相关技术栈 五、UI概览 六、待完善部分 总结&#xff1a; 前言 我最初开始接触Web自动化测试的时候&#xff0c;没有直接的领路人&#xff0c;测试行业知识也远不及如今这么丰富和易获取&#xff0c;当时我对于…

Hello算法学习笔记之搜索

一、二分查找 1.从数组中找到target的索引 注意&#xff1a;while条件是< O&#xff08;logn&#xff09; 二分查找并非适用于所有情况&#xff0c;原因如下&#xff1a; 二分查找仅适用于有序数据。若输入数据无序&#xff0c;为了使用二分查找而专门进行排序&#xff…

视频处理器对LED显示屏的作用

视频处理器在LED显示屏中扮演着重要的角色&#xff0c;其作用如下&#xff1a; 图像和视频信号处理&#xff1a;视频处理器负责对输入的图像和视频信号进行处理和优化&#xff0c;以确保在LED显示屏上呈现出高质量的图像和视频内容。它可以对图像进行去噪、锐化、色彩校正、亮度…

【数据结构】复杂度

目录 &#x1f4d6;什么是数据结构&#xff1f;&#x1f4d6;什么是算法&#xff1f;&#x1f4d6;算法效率&#x1f4d6;时间复杂度&#x1f516;大O的渐进表示法&#x1f516;常见时间复杂度计算举例&#x1f516;面试题&#xff1a;消失的数字 &#x1f4d6;空间复杂度&…

I2C协议应用(嵌入式学习)

I2C协议&应用 0. 前言1. 概念2. 特点&工作原理3. 应用示例代码模板HAL模板 0. 前言 I2C是Inter-Integrated Circuit的缩写&#xff0c;它是一种广泛使用的串行通信协议。它由飞利浦&#xff08;现在是NXP Semiconductors&#xff09;开发&#xff0c;并已成为各种电子…

无迹卡尔曼滤波在目标跟踪中的作用(一)

在前一节中&#xff0c;我们介绍了扩展卡尔曼滤波算法EKF在目标跟踪中的应用&#xff0c;其原理是 将非线性函数局部线性化&#xff0c;舍弃高阶泰勒项&#xff0c;只保留一次项 &#xff0c;这就不可避免地会影响结果的准确性&#xff0c;除此以外&#xff0c;实际中要计算雅各…