【Qt】多线程

QThread 类简介

一个 QThread 类的对象管理一个线程。在设计多线程程序的时候,需要从 QThread 继承定义线程类,并重新定义 QThread 的虚函数 run(),在函数 run() 里处理线程的事件循环。

应用程序的线程称为主线程,创建的其他线程称为工作线程。一般会在主线程里创建工作线程,并用函数 start() 开始执行工作线程的任务。函数 start() 会在其内部调用函数 run() 进入工作线程的事件循环,函数 run() 的程序体一般是一个无限循环,可以在函数 run() 里调用函数 exit() 或 quit() 结束线程的事件循环,或在主线程里调用函数 terminate() 强制结束线程。

QThread 常用 API

函数功能
run()线程的入口函数
start()通过调用 run() 开始执行线程。操作系统将根据优先级参数调度线程。如果线程已经在运行,这个函数什么也不做
cuttentThread()返回一个指向管理当前执行线程的QThread的指针
isRunning()如果线程正在运行则返回 true,否则返回 false
sleep() / msleep() / usleep()使线程休眠,单位为秒 / 毫秒 / 微秒
wait()阻塞线程,直到满足以下任何一个条件:
与此 QThread 对象关联的线程已经完成执行(即当它从run() 返回时)。如果线程已经完成,这个函数将返回 true。如果线程尚未启动,它也返回 true。
已经过了几毫秒。如果时间是 ULONG_MAX(默认值),那么等待永远不会超时(现成必须从 run() 返回)。如果等待超时,此函数将返回false。
terminate()终止线程的执行。线程可以立即终止,也可以不立即终止,取决于操作系统的调度策略。在 terminate() 之后使用 QThread::wait() 来确保。
finished()当线程结束时会发出该信号,可以通过该信号来实现线程的清理工作。

使用线程

创建线程的步骤:

  1. 自定义一个类,继承于 QThread,并且只有一个线程处理函数(和主线程不是同一个线程),这个线程处理函数就是重写父类中的 run() 函数。
  2. 线程处理函数里面写入需要执行的复杂数据处理。
  3. 启动线程不能直接调用 run() 函数,需要使用对象来调用 start() 函数实现线程启动。
  4. 线程处理函数执行结束后可以定义一个信号来告诉主线程。
  5. 最后关闭线程。

示例:
创建 Qt 项目,UI界面如下:
在这里插入图片描述

再通过 New File 对话框新建一个 C++ 类,继承于 QThread 类:
在这里插入图片描述
创建结束后会生成文件 mythread.h 和 mythread.cpp。其中 mythread.h:

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QWidget>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
    
    void run();//线程任务函数
    
signals:
    //声明信号函数
    void sendTime(QString Time);
};

#endif // MYTHREAD_H

mythread.cpp:

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

MyThread::MyThread(QObject *parent)
    : QThread{parent}
{}

void MyThread::run()
{
    while(1)
    {
        QString time = QTime::currentTime().toString("hh:mm:ss");
        
        qDebug() << time;
        
        //发送信号
        emit sendTime(time);
        
        sleep(1);
    }    
}

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 showTime(QString Time);

private:
    Ui::Widget *ui;
    MyThread myThread;
};
#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(&myThread, &MyThread::sendTime, this, &Widget::showTime);
}

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

void Widget::on_pushButton_clicked()
{
    myThread.start();
}

void Widget::showTime(QString Time)
{
    ui->label->setText(Time);
}

运行结果:

在这里插入图片描述

线程同步

在多线程程序中,由于存在多个线程,线程之间可能需要访问同一个变量,或一个线程需要等待另一个线程完成某个操作后才产生相应的动作。在 Qt 中实现线程互斥和同步常用的类有:
互斥锁:QMutex、QMutexLocker
条件变量:QWaitCondition
信号量:QSemaphore
读写锁:QReadLocker、QWriteLocker、QReadWriteLock

互斥锁

互斥锁是一种保护和防止多个线程同时访问同一对象实例的方法,在 Qt 中,互斥锁主要是通过 QMutex 类来处理。
QMutex: QMutex 是 Qt 框架提供的互斥锁类,用于保护共享资源的访问,实现线程间的互斥操作。
用途: 在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。
下面有两个线程,每个线程对 num 进行五万次的加加操作,结果应该是十万:

在这里插入图片描述

但结果显然不是十万,这是因为一个线程对 num 加加后,又被另一个线程写入(num 加加非原子操作,有多步),这就导致前一个线程没有正确加加。这就是多个线程访问同一个变量导致的后果,此时我们通过 QMutex 类对访问变量的操作加锁即可得到正确结果:

在这里插入图片描述

注意:
在这里插入图片描述

这里的 mutex 相当于一把钥匙,如果两个线程要访问同一个共享资源,例如本例的 num,就需要通过 lock() 拿到这把钥匙,然后才可以访问该共享资源,在完成加加操作时,别的线程无法访问 num,就可以正确进行加加,访问完之后还要通过 unlock() 还回钥匙,别的线程才有机会拿到钥匙(才可以访问共享资源)。

QMutexLocker: QMutexLocker 是 QMutex 的辅助类,使用 RAII(Resource Acquisition Is Initialization)方式对互斥锁进行上锁和解锁操作。
用途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致死锁等问题。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值