【QT5】<重点> QT多线程

文章目录

前言

一、QThread创建多线程

二、QMutex基于互斥量的同步

三、QReadWriteLock线程同步

四、QWaitCondition线程同步

五、QSemaphore基于信号量的同步


前言

本篇记录学习QT多线程的知识,参考视频13.1QThread创建多线程程序_哔哩哔哩。若涉及版权问题,请联系作者删除!


一、QThread创建多线程

1. 使用步骤:

  • ①创建的类需要继承QThread类。
  • ②重写run函数,新建的线程会执行run函数。
  • ③线程开启:对象调用start方法,使线程执行run函数。
  • ④线程终止:
    • 对象调用terminate方法,提示线程不再执行run函数。
    • 对象调用wait方法,等待线程执行完毕。
  • ⑤线程销毁:动态申请new需要调用deleteLater方法销毁线程对象。(该函数可以放置于run函数内,当run函数执行完毕后就会销毁该线程对象,防止内存泄漏)注意:尽量少用静态申请栈空间的方式创建线程对象,因为很可能该对象销毁时线程仍在执行,就会报错。

**2. 案例演示:**创建一个类继承QThread,在重写的run方法中根据是否暂停来随机生成骰子值。

【1】实现效果:

【2】dicethread.h:

cpp 复制代码
#ifndef DICETHREAD_H
#define DICETHREAD_H

#include <QThread>

class DiceThread : public QThread
{
    Q_OBJECT

public:
    DiceThread();
    ~DiceThread();

private:
    void run() override;

signals:
    void diceGenerate(int diceCount, int diceValue);

private slots:
    void stopDiceThread_slot();
    void startGenerate_slot();
    void pauseGenerate_slot();

private:
    int diceCount = 0;  //第几次生成骰子
    int diceValue = 1;  //骰子的值
    bool pauseFlag = true;  //暂停生成骰子
    bool stopFlag = true;   //线程结束标志
};

#endif // DICETHREAD_H

【3】dicethread.cpp:

cpp 复制代码
#include "dicethread.h"
#include <QRandomGenerator>

DiceThread::DiceThread()
{

}

DiceThread::~DiceThread()
{

}

/* 线程执行函数 */
void DiceThread::run()
{
    stopFlag = false;
    while (!stopFlag) {
        if (!pauseFlag) {
            diceValue = QRandomGenerator::global()->bounded(1, 7);
            emit diceGenerate(++diceCount, diceValue);
        }
        msleep(500);
    }
}

/* 接收到线程终止信号 */
void DiceThread::stopDiceThread_slot()
{
    stopFlag = true;
}

/* 接收到开始生成信号 */
void DiceThread::startGenerate_slot()
{
    pauseFlag = false;
}

/* 接收到停止生成信号 */
void DiceThread::pauseGenerate_slot()
{
    pauseFlag = true;
}

【4】widget.h:

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "dicethread.h"

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

signals:
    void stopDiceThread();  //线程停止,DiceThread内部修改标志位
    void startGenerate();   //开始生成,DiceThread内部修改标志位
    void pauseGenerate();   //暂停生成,DiceThread内部修改标志位

private slots:
    void on_btnStartThread_clicked();
    void diceGenerate_slot(int, int);//接收DiceThread的骰子信息
    void on_btnStopThread_clicked();
    void on_btnDiceBegin_clicked();
    void on_btnDiceEnd_clicked();
    void on_btnClearText_clicked();

private:
    Ui::Widget *ui;
    DiceThread *dice = nullptr;
};
#endif // WIDGET_H

【5】widget.cpp:

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QString>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //设置按钮互斥
    ui->btnStartThread->setEnabled(true);
    ui->btnStopThread->setEnabled(false);
    ui->btnDiceBegin->setEnabled(false);
    ui->btnDiceEnd->setEnabled(false);
}

Widget::~Widget()
{
    delete ui;
}

/* 槽函数:接收diceGenerate信号*/
void Widget::diceGenerate_slot(int diceCount, int diceValue)
{
    ui->plainTextEdit->appendPlainText("第" + QString::number(diceCount) + "次,生成点数为:"
                                       + QString::number(diceValue));
}

/* "启动线程"按钮 */
void Widget::on_btnStartThread_clicked()
{
    //创建DiceThread对象
    dice = new DiceThread;
    connect(dice, SIGNAL(diceGenerate(int, int)), this, SLOT(diceGenerate_slot(int, int)));
    connect(this, SIGNAL(stopDiceThread()), dice, SLOT(stopDiceThread_slot()));
    connect(this, SIGNAL(startGenerate()), dice, SLOT(startGenerate_slot()));
    connect(this, SIGNAL(pauseGenerate()), dice, SLOT(pauseGenerate_slot()));
    //启动线程
    dice->start();
    ui->plainTextEdit->appendPlainText("线程已启动");
    //设置按钮互斥
    ui->btnStartThread->setEnabled(false);
    ui->btnStopThread->setEnabled(true);
    ui->btnDiceBegin->setEnabled(true);
    ui->btnDiceEnd->setEnabled(false);
}

/* "停止线程"按钮 */
void Widget::on_btnStopThread_clicked()
{
    dice->terminate();
    dice->wait();
    dice->deleteLater();
    ui->plainTextEdit->appendPlainText("线程已停止");
    emit stopDiceThread();
    //设置按钮互斥
    ui->btnStartThread->setEnabled(true);
    ui->btnStopThread->setEnabled(false);
    ui->btnDiceBegin->setEnabled(false);
    ui->btnDiceEnd->setEnabled(false);
}

/* "开始"按钮 */
void Widget::on_btnDiceBegin_clicked()
{
    emit startGenerate();
    ui->btnDiceBegin->setEnabled(false);
    ui->btnDiceEnd->setEnabled(true);
}

/* "暂停"按钮 */
void Widget::on_btnDiceEnd_clicked()
{
    emit pauseGenerate();
    ui->btnDiceBegin->setEnabled(true);
    ui->btnDiceEnd->setEnabled(false);
}

/* "清空"按钮 */
void Widget::on_btnClearText_clicked()
{
    ui->plainTextEdit->clear();
}

二、QMutex基于互斥量的同步

**1. 背景:**当多个线程同时访问一块内存区域时,就会出现错误,因此需要线程同步。

**2. QMutex:**在互斥量之前上锁,然后在一个线程使用完互斥量之后解锁。如果一个线程试图向一个已经被其它线程上锁的互斥量上锁的话,这个线程将被阻塞,直到这个互斥量被解锁。

3. 基本使用:

  • lock():上锁
  • unlock():解锁
  • tryLock():尝试上锁,若成功则上锁,不成功则返回false

4. 案例演示:

创建两个线程来同时访问value,通过查看相关调试信息来获取是哪个线程修改了value的值。我们是想让两个线程交叉着访问value。

【1】不加锁的代码:

cpp 复制代码
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT

public:
    MyThread();
    void run() override;
    void setNum(int *num);

private:
    int *num;
};

#endif // MYTHREAD_H
cpp 复制代码
#include "mythread.h"

MyThread::MyThread()
{

}

void MyThread::run()
{
    while (*num > 0) {
        (*num)--;
        qDebug("线程号: %d, value = %d", QThread::currentThreadId(), *num);
    }
}

void MyThread::setNum(int *num)
{
    this->num = num;
}
cpp 复制代码
#include "widget.h"
#include <QApplication>
#include "mythread.h"

int main(int argc, char *argv[])
{
    int value = 20;
    MyThread thread1, thread2, thread3;
    thread1.setNum(&value);
    thread2.setNum(&value);
    thread3.setNum(&value);
    thread1.start();
    thread2.start();
    thread3.start();
    while (1) {}
    return 0;
}

从运行结果中,我们能够看出value递减打印出现问题,这不是我们想要的

【2】使用QMutex后:就不会出现上述的问题。


三、QReadWriteLock线程同步

**1. 说明:**使用互斥量时存在一个问题,每次只能有一个线程获得互斥量的权限。如果多个线程读取某个变量,就会出现排队现象。而实际上,有需求让多个线程同时读取。因此,引入QReadWriteLock实现线程同步。

2. 主要函数:

  • lockForRead():只读方式锁定资源,如果有其他线程以写入方式锁定,这个函数会阻塞。
  • lockForWrite():以写入方式锁定资源,如果其他线程以读或写模式锁定资源,则函数堵塞。
  • unlock():解锁。
  • tryLockForRead():是lockForRead的非阻塞版本。
  • tryLockForWrite():是lockForWrite的非阻塞版本。

四、QWaitCondition线程同步

**1. 说明:**在著名的"生产者-消费者"模型中,生产者负责制作蛋糕,消费者负责拿走蛋糕。此时,生产者制作蛋糕完成后应该通知消费者,因此引入QWaiteCondition实现线程同步,需要搭配QMutex锁。

2. 常用函数:

  • **wait(QMutex *lockedMutex):**进入等待状态,解锁互斥量lockedMutex,被唤醒后锁定lockedMutex并退出函数。
  • **wakeAll():**唤醒所有处于等待状态的线程,唤醒顺序不确定。
  • **wakeOne():**唤醒一个处于等待状态的线程,唤醒哪个不确定。

五、QSemaphore基于信号量的同步

**1. 说明:**信号量和互斥量的区别在于,信号量可以被多个线程同时访问,而互斥量只能被一个线程访问。同时,在"生产者-消费者"模型中,可能会有多个生产者生产蛋糕,生产者最多可以容纳10个蛋糕,然后消费者在蛋糕生产出来后就可以拿走。因此,引入QSemaphore实现基于信号量的线程同步。

2. 注意:

  • QSemaphore 类中的信号量实际上是一个计数器,它用于记录能够同时访问某个共享资源的线程数。

3. 常用函数:

  • **acquire(int n):**尝试获得n个资源,如果不够将堵塞线程,直到n个资源可用。调用后,信号量计数器-1.
  • **release(int n):**释放资源,如果资源已经全部可用,则可扩充资源总数。调用后,信号量计数器+1.
  • **int available():**返回当前信号量的资源个数。
  • **bool tryAcquire(int n=1):**尝试获取n个资源,不成功时,不阻塞线程。
相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00616 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术16 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript