QT教程:多线程&互斥锁(互斥锁有实例)

一、主线程
每个应用程序启动后,拥有的第一个线程称为主线程。QT中创建的第一个桌面应用程序(QDialog、QMainwindow or QWidget)开始运行,那么这个桌面应用程序就是主线程,也成为GUI线程。另外,子线程即工作线程,主要负责处理GUI线程的耗时工作(耗时工作例如,开启视频、打开图片的同时,通过window串口向GUI界面传输数据),QT中所有的组件类和几个相关的类只能工作在GUI线程,不能工作在子线程。

二、子线程
QThread线程类是Qt线程中一个公共的线程类,所有的线程类都是从QThread线程中派生出来,派生的线程需要重写虚函数run,通过在主线程中启动子线程来调用子线程的run函数。
QThread类提供不依赖于平台的管理线程的方法,一个QThread类的对象管理一个线程,一般从QThread继承一个自定义类,并重定义虚函数run,在run函数里实现线程需要完成的任务。
将应用程序的线程称为主线程,额外创建的线程称为工作线程,一般在主线程里创建工作线程,并调用start()开始执行工作线程的任务。start()会在内部调用run函数,进入工作线程的事件循环,在run函数里调用exit()或quit()可以结束线程的事件循环,或在主线程里调用terminate()强制结束线程。

子线程创建的两种方法:
(1)继承自QThread类,需要重写虚函数run

a.自定义一个线程类,继承自QThread
b.在主线程里面对这个类进行实例化
c.在主线程里面启动子线程
d.主线程和子线程通过信号与槽进行通信

(2)将新建的类放到线程中去运行----->moveToThread(),不需要重写虚函数run

a.用户自己创建一个类
b.在主线程里面去实例化该类
c.将该类放到我们的线程中去运行,moveToThread
d.启动线程
e.主线程和子线程通过信号与槽机制进行通信

三、多线程
一个应用程序一般只有一个线程,一个线程内的操作是顺序执行的,如果有某个比较消耗时间的计算或操作,比如网络通信中的文件传输,在一个线程内操作时,用户界面就可能会冻结而不能及时响应。在这种情况下,可以创建一个单独的线程类执行比较消耗时间的操作,并与主线程之间处理好同步与数据交互,这就是多线程应用程序。
Qt为多线程的操作提供了完整的支持,QThread是线程类,是实现多线程操作的核心类,一般从QThread继承定义自己的线程类,线程之间的同步是其交互的主要问题,Qt提供了QMutext、QMutexLocker、QReadWriteLock、QwaitCondition、QSemaphore等多种类用于实现线程之间的同步
多线程的概念:最简单的理解就是有多个线程,用户可以自行创建多个线程,但是必须要有一个主线程。
多线程的本质:

1)并发性,并发性是多线程的本质特征
(2)在宏观上,所有线程并行执行
(3)多线程之间相互独立,互不干涉

多线程的声明周期:
新建:从新建一个线程对象到start之前的状态,都是新建状态
就绪:线程调用start之后,就处于就绪状态,等待线程调度器的调度
运行:就绪状态下的线程在获取CPU资源后就可以执行run(),此时的线程就处于运行状态,运行的线程可变为就绪、阻塞及死亡三种状态
等待/阻塞/睡眠:在一个线程执行了sleep(睡眠)、suspend(挂起)等方法,从而进入阻塞状态,在睡眠结束后方可进入就绪状态
终止:run()方法完成后或发生其它终止条件时就会切换到终止状态

四、线程的同步与互斥
1、线程同步基础
同步:
是指散布在不同任务之间的若干程序片段,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。例如,两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如任务A的运行依赖于B任务产生的数据(故这时候产生了一个内置的顺序即由B到A)
同步:完成同一任务的线程之间,需要协调它们的工作而相互等待、交互
2、线程互斥基础
互斥:
是指散布在不同任务之间的若干程序片段,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完成这个程序片段后才可以运行,例如,一个公共资源,同时被A和B两个线程访问,这时候就不能出现同时访问该公共资源,只有等其中一个线程访问完成之后,另一个线程才可以访问该资源。
(1)临界资源
每次只能允许一个线程进行访问的资源,称为临界资源。例如,对于一个公共厕所的一个蹲位,每次只能允许一个人进入该蹲位上厕所。
(2)线程互斥
在多线程中,多个线程在同一时刻,同时访问临界资源,这时候我们就成为线程互斥。例如,对于一个公共厕所的一个蹲位,前一个人上完厕所之后,该厕所就被空出来了,而后,无序的人群都想要去上厕所,这时候就导致了一个资源被多个人同时访问,故在线程中形成互斥。
(3)互斥锁(QMutex)
针对线程互斥而提出的一种多个线程访问同一资源的问题,在资源中进行加锁操作(lock),加锁操作后,如果这时候有两个线程同时访问同一资源,就会出现什么情况呢?这时候会出现,如果线程1访问到了该资源,线程2就必须等待线程1将资源访问完成之后,再去访问该资源,而不是与线程1进行资源的抢夺(当然,对资源的抢夺是随机的,有可能线程1先抢到资源,线程2后抢到资源,也有可能是线程2先抢到资源,线程1后抢到资源)。例如,对于一个公共厕所的一个蹲位,现在后面有多个人想要获得这个蹲位,进行上厕所操作,这时候就出现互斥的情况。解决这种办法就是读该厕所安装一把锁(即互斥锁),路人1上厕所时,就把该厕所反锁住,路人2、路人2、路人4等等都无法进入该厕所上厕所,就只能等路人1厕所上完之后,将该厕所给空出来,后面的路人2、路人3等才能进行上厕所。注意互斥锁lock和unlock(trylock和unlock)必须一起用才能有效果。

dialog.h

#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>
#include <QMutex>
#include <QThread>

namespace Ui {
class Dialog;
}

//第一个线程
class Thread1 : public QThread
{
public:
    void run();//thread1的运行虚函数


};
//第二个线程
class Thread2 : public QThread
{
public:
    void run();//thread2的运行虚函数

};



class Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Dialog(QWidget *parent = nullptr);
    ~Dialog();
    static void func1();
    static void func2();
    static QMutex mutex;

private:
    Ui::Dialog *ui;
    Thread1 *thread1;
    Thread2 *thread2;


    static int number; //定义一个资源



};

#endif // DIALOG_H

dialog.cpp

#include "dialog.h"
#include "ui_dialog.h"
#include <QDebug>

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);
//    func1();//函数正常调用,执行number
//    func2();//函数正常调用,执行number
    //在主线程中实例化子线程
    thread1 = new Thread1;
    thread2 = new Thread2;
    //在主线程中开启子线程的运行
    thread1->start();//开启子线程1的运行
    thread2->start();//开启子线程2的运行
//    thread1->exit();
//    thread2->exit();


}

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

void Dialog::func1()
{
    mutex.lock();
    qDebug()<<"线程one已经运行";
    number +=50; //number=50
    qDebug()<<"func1中的第一个number="<<number;
    qDebug()<<"thread1已经运行";
    number -=10; //number=40
    qDebug()<<"func1中的第二个number="<<number;
    qDebug()<<"线程1已经运行";
    mutex.unlock();
}
QMutex Dialog::mutex; //分配空间

void Dialog::func2()
{
    mutex.lock();//如果不加锁的话,会出现两个线程争夺一个资源的情况
    qDebug()<<"线程two已经运行";
    number *=3;
    qDebug()<<"func2中的第一个number="<<number;
    qDebug()<<"线程thread2已经运行";
    number /=2; //number=60
    qDebug()<<"func2中的第二个number="<<number;
    qDebug()<<"线程2已经运行";
    mutex.unlock();
}

//两个子线程同时访问同一资源number
void Thread1::run()//线程1访问资源number
{
    Dialog::func1();
    Dialog::func2();
}

void Thread2::run()//线程2访问资源number
{
    Dialog::func1();
    Dialog::func2();
}

main.cpp

#include "dialog.h"
#include <QApplication>

int Dialog::number=0;
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Dialog w;
    w.show();

    return a.exec();
}

运行结果如下:

线程one已经运行
func1中的第一个number= 50
thread1已经运行
func1中的第二个number= 40
线程1已经运行
线程one已经运行
func1中的第一个number= 90
thread1已经运行
func1中的第二个number= 80
线程1已经运行
线程two已经运行
func2中的第一个number= 240
线程thread2已经运行
func2中的第二个number= 120
线程2已经运行
线程two已经运行
func2中的第一个number= 360
线程thread2已经运行
func2中的第二个number= 180
线程2已经运行

总结:

》》表明使用的互斥锁已经已经将资源number给锁住,thread1使用func1中的资源number时,thread2阻塞,等thread1将func1中的number资源使用完之后,thread2才使用func1中的number资源;同理,func2中的number资源也一样。

  • 16
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
Qt中使用MQTT多线程可以通过以下步骤实现: 1. 导入MQTT库:在Qt项目中,首先需要导入MQTT库。可以使用Eclipse Paho或者Qt MQTT等库来实现。 2. 创建MQTT客户端:使用MQTT库提供的API创建一个MQTT客户端对象。 3. 连接到MQTT代理:使用客户端对象的`connectToHost`方法连接到MQTT代理服务器。 4. 创建多线程:使用Qt提供的多线程机制,例如`QThread`类,创建一个新的线程。 5. 在新线程中执行订阅和发布操作:在新线程中创建一个类,继承自`QThread`,重写其`run`方法。在`run`方法中执行MQTT订阅和发布操作。 . 启动新线程:实例化新线程对象,调用`start`方法启动线程。 下面是一个简单的示例代码: ```cpp #include <QtMqtt/QtMqtt> class MqttThread : public QThread { public: void run() override { // 在这里执行订阅和发布操作 } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // 创建MQTT客户端 QMqttClient client; // 连接到MQTT代理 client.connectToHost(); // 创建多线程 MqttThread mqttThread; // 启动新线程 mqttThread.start(); return a.exec(); } ``` 在`MqttThread`类中,你可以实现自己的订阅和发布逻辑。注意,在多线程环境下,要确保对MQTT客户端的访问是线程安全的。可以使用互斥锁等机制来保证线程安全性。 这只是一个简单的示例,实际应用中可能需要更复杂的逻辑和错误处理。具体实现还需要根据你的项目需求进行调整。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cheems_Pak Choi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值