Qt_线程介绍与使用

目录

1、QThread常用API

2、Qt线程安全 

3、使用线程QThread

4、connect函数的第五个参数 

5、Qt互斥锁

5.1 QMutexLocker

6、条件变量 

7、信号量 

结语


前言:

        线程是应用程序开发非常重要的概念,在Qt中,用QThread类来实现多线程,将线程有关的各种函数都封装到这个类中(包括线程执行函数),方便通过该类对线程进行控制。若要创建一个线程执行某些任务,则需要自定义一个继承QThread的类,重写线程执行函数让该线程执行我们期望的任务。

1、QThread常用API

         使用QThread创建一个线程对象后,需要调用相关API来操作线程,比如启动线程,等待线程等操作以及获取线程信息,这些成员函数的介绍如下:

run()
线程的执行函数,即线程所执行的任务,通过start函数间接调用他
start()
真正意义上的创建线程,调用此函数才能让线程执行run函数的内容
currentThread()
返回⼀个指向管理当前执行线程的QThread对象的指针
isRunning()
如果线程正在运行返回true,否则返回false
sleep() / msleep() / usleep()
使线程休眠,单位为秒 / 毫秒 / 微秒
wait()
线程等待,调用此函数的线程会阻塞在此函数处,若线程的run函数调用结束或者线程没有被启动,wait返回true。如果等待超时,此函数将返回false
terminate()
终止线程的执行
finished()
当线程结束时会发出该信号

2、Qt线程安全 

         线程安全指的是多个线程修改共享资源时会发生意料之外的错误,而Qt中的共享资源毫无疑问是界面本身,而对界面的修改默认是在主线程,如果我们新建线程并对界面进行修改就会引发线程安全问题,因此Qt中严格规定新建的线程不能对界面进行修改!

        若需要使用线程对界面进行修改,则采用信号与槽的方式,线程向主线程发送信号,主线程连接该信号与槽,在槽函数中进行对界面的修改,因为槽函数的执行是在主线程,因此不会出现线程安全问题。如下图:

3、使用线程QThread

        用QLabel模拟一个时间表,首先在界面上创建一个按钮(pushbutton)和一个标签(label),按钮的作用是让线程开始执行任务,并将任务的结果打印到标签中,至此模拟出时间表。界面设计如下:


        1、首先要实现上述功能,必须先创建一个继承自QThread的类,点击新建项目选择新建类:


        2、然后自定义类名,并让该类继承QThread,如下:


        3、创建完毕后会自动生成相关的文件和代码,我们需要做的是重写run函数,让该线程能够执行我们期望的任务,并且还要给该类自定义一个信号,目的是通过发送信号间接修改界面,mythread.h文件代码如下:

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QWidget>
#include <QThread>

class mythread : public QThread
{
    Q_OBJECT
public:
    mythread();
    
    void run();//重写run函数
   
signals:
    void mythread_signal(QString time);//自定义的信号
};

#endif // MYTHREAD_H

        mythread.cpp文件代码如下:

#include "mythread.h"
#include <QTime>
#include <QDebug>

mythread::mythread()
{

}

void mythread::run()
{
    while(1)
    {
        QString time = QTime::currentTime().toString("hh:mm:ss");
        qDebug()<<time;
        emit mythread_signal(time);//将时间传过去
        sleep(1);//每过一秒更新一次
    }
    
}

        4、上述线程的工作已经完成,现在需要在主线程中实例化线程对象,并在主线程中调用线程的start函数让线程跑起来,可以让按钮完成启动线程的工作,并且实现线程信号对应的槽函数,在该槽函数中进行对label的文本设置,widget.h文件代码如下:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <mythread.h>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButton_clicked();

    void thread_slot(QString time);//线程信号的槽函数

private:
    mythread thread;//要想启动线程,必须创建线程对象
    Ui::Widget *ui;
};
#endif // WIDGET_H

        widget.cpp代码如下:

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    connect(&thread,&mythread::mythread_signal,this,&Widget::thread_slot);
}

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


void Widget::on_pushButton_clicked()
{
    thread.start();//启动线程
}

void Widget::thread_slot(QString time)
{
    ui->label->setText(time);//设置文本
}

        运行结果:

        至此实现了使用线程将label标签设置为时间表的功能。整个程序的逻辑:点击按钮启动线程的start函数->线程开始执行run函数并不断的发送信号->主线程收到线程的信号后执行对应的槽函数实现label文本的更新

4、connect函数的第五个参数 

        connect函数是用于连接信号与槽的,connect函数第五个参数为Qt::ConnectionType,用于指定信号与槽的连接类型,主要影响槽函数的执行逻辑,一般在多线程的情况下才会用到第五个参数。Qt::ConnectionType提供了以下五种方式:

Qt::AutoConnection
在Qt中,会根据信号和槽函数是否处于统一线程自动选择连接类型。如果信号和槽函数在同⼀线程中,就会Qt:DirectConnection类型;如果它们位于不同的线程中,则使用Qt::QueuedConnection类型。
Qt::DirectConnection
当信号发出时,槽函数会立即在同⼀线程中执行,可以理解为就是简单的一次函数调用。
Qt::QueuedConnection
当信号发出时,槽函数会被插⼊到接收对象所属的线程的事件队列中,等待下⼀次事件循环时执行。
Qt::BlockingQueuedConnection
与Qt:QueuedConnection类似,但是发送信号的线程会被阻塞,直到槽函数执行完毕。
Qt::UniqueConnection
这是⼀个标志,可以使用位运算和上述任何⼀种连接类型组合使用。

5、Qt互斥锁

        讲到多线程就自然离不开互斥锁的使用,由于多线程很容易导致线程安全问题,因此使用互斥锁限制多个线程对共享资源的访问。在Qt中,互斥锁主要是通过QMutex类来实现。互斥锁的使用如下。

        1、首先创建一个继承QThread的类thread,并在该类中创建一把锁和一个静态变量,thread.h文件代码如下:

#ifndef THREAD_H
#define THREAD_H

#include <QWidget>
#include <QThread>
#include <QMutex>

class mythread : public QThread
{
    Q_OBJECT
public:
    mythread();

    void run();
    static int num;//让多个线程对该值进行++
private:
    static QMutex mutex;//让线程看到同一把锁
};

#endif // THREAD_H

        2、定义run函数,thread.cpp代码如下:

#include "thread.h"

int mythread::num = 0;
QMutex mythread::mutex;

mythread::mythread()
{

}

void mythread::run()
{
    for(int i = 0;i<100000;i++)
    {
        mutex.lock();
        num++;
        mutex.unlock();
    }
}

        3、在widget.cpp中创建两个线程,这两个线程同时对num进行++操作,最终观察num的值,代码如下:

#include "widget.h"
#include "ui_widget.h"
#include "thread.h"
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    mythread t1;
    mythread t2;
    t1.start();
    t2.start();

    t1.wait();
    t2.wait();

    qDebug()<<mythread::num;
}

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

        运行结果:

        此时结果是预期的,因为对访问共享资源进行加锁限制,如果上述代码没有加锁,结果如下:

         从这里也可以看到加锁的必要性。

5.1 QMutexLocker

         QMutexLocker是QMutex的辅助类,使用RAII(Resource Acquisition Is Initialization)方式 对互斥锁进行自动上锁和解锁的操作。具体来说,他会在被创建时自动上锁,在作用域结束后自动释放锁,避免开发者忘记解锁导致的死锁等问题。

        将上述代码的上锁解锁操作用QMutexLocker代替:

#include "thread.h"

int mythread::num = 0;
QMutex mythread::mutex;

mythread::mythread()
{

}

void mythread::run()
{
    for(int i = 0;i<100000;i++)
    {
        QMutexLocker locker(&mutex);//创建时自动加锁
        //mutex.lock();
        num++;
        //mutex.unlock();
    }//出了作用域后自动结束
}

        运行结果:

6、条件变量 

         在Qt中,QWaitCondition类表示条件变量,条件变量是作用是让线程实现同步机制,线程同步是为了让所有线程申请到锁的能力是一样的,上述例子中,线程t1和线程t2虽然实现了互斥,但是不具备同步机制,可以通过观察执行线程的地址来判断申请锁的能力,如下图:


         添加条件变量后,节选widget.cpp代码: 

#include "widget.h"
#include "ui_widget.h"
#include "thread.h"
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    mythread t1;
    mythread t2;
    t1.start();
    t2.start();

    for(int i = 0;i<1000;i++)
    {
        _sleep(1);
        mythread::cond.wakeAll();//唤醒条件变量
    }

    t1.wait();
    t2.wait();

    qDebug()<<mythread::num;

}

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

        测试结果:

        可以发现两个线程依次有序的对共享资源进行++操作。 

7、信号量 

        信号量类似于加强版互斥锁,不仅能完成上锁和解锁操作,而且还可以限制访问共享资源的线程数量。Qt中用QSemaphore类来表示信号量,用于控制同时访问共享资源的线程数量,测试代码如下:

#include "thread.h"
#include <QDebug>
#include <QSemaphore>

int mythread::num = 0;
QMutex mythread::mutex;
QWaitCondition mythread::cond;//初始化条件变量

QSemaphore QS(1);//信号量为1,表示只能有一个线程申请到信号量

mythread::mythread()
{

}

void mythread::run()
{

    for(int i = 0;i<1000;i++)
    {
        QS.acquire();//尝试获取信号量,若已满则阻塞
        //QMutexLocker locker(&mutex);
        //cond.wait(&mutex);//条件变量等待
        //mutex.lock();
        num++;
        qDebug()<<this;
        //mutex.unlock();
        QS.release();//释放信号量
    }
}

结语

        以上就是关于Qt线程介绍与使用,在Qt中使用线程最为重要的就是不能直接在线程中对界面进行修改,并且Qt线程采用的是继承的思想,和C++中的线程库采用回调的方式有些不同。线程的其他相关概念都相差无几。

        最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

安权_code

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

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

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

打赏作者

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

抵扣说明:

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

余额充值