【Qt】多线程

目录

一、常用API

二、线程安全

2.1 互斥锁

2.2 条件变量

2.3 信号量 


在Qt中,多线程的处理一般是通过QThread类来实现

QThread代表一个在应用程序中可以独立控制的线程,也可以和进程中的其他线程共享数据。QThread对象管理程序中的一个控制线程

一、常用API

run()线程入口函数
start()

通过调用run()函数开始执行线程,操作系统根据优先级参数调度线程

若线程已在运行,该函数说明也不做

currentThread()返回一个指向 管理当前执行线程 的QThread的指针
isRunning()若线程正在运行返回true,否则返回false
sleep()/msleep()/usleep()实现线程休眠,单位为秒/毫秒/微秒
wait()

阻塞线程,直到满足以下任何一个条件:

与此QThread对象关联的线程已经完成执行(即当它从run()返回时),若线程已经完成,这个函数将返回true;若线程尚未启动,也返回true

已经过了几毫秒,若时间是ULONG_MAX(默认值),那么等待永远也不会超时(线程必须从run()返回)。若等待超时,此函数返回false

与POSIX pthread_join() 函数类似

terminate()终止线程的执行。线程可以立即终止,也可以不立即终止,取决于操作系统的调度策略。在terminate()之后使用QThread::wait()来确保终止
finished()当线程结束时会发出该信号,可以通过该信号来实现线程的清理工作

创建线程的步骤:

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

代码示例

新建 Qt 项目,设计UI界面如下:

新建一个类,继承于QThread类

thread.h

#ifndef THREAD_H
#define THREAD_H

#include <QWidget>
#include <QThread>
#include <QTime>
#include <QDebug>

class Thread : public QThread
{
    Q_OBJECT
public:
    Thread();
public:
    void run();//重写
signals:
    void sendTime(QString time);//声明信号函数
};

#endif // THREAD_H

thread.cpp

#include "thread.h"

Thread::Thread() {}

void Thread::run()
{
    while(true)
    {
        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 "thread.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 startShow();
    void showTime(QString time);

private:
    Ui::Widget *ui;
    Thread thread;//声明线程对象
};
#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(ui->pushButton, &QPushButton::clicked, this, &Widget::startShow);
    connect(&thread, &Thread::sendTime, this, &Widget::showTime);
}

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

void Widget::startShow()
{
    thread.start();
}

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

注意:

  • 线程函数内部不允许操作 UI 图形界面,一般用数据处理
  • connect() 函数第五个参数表的为连接的方式,只有在多线程时才有意义

二、线程安全

实现线程互斥和同步常用的类有:

  • 互斥锁:QMutex、QMutexLocker
  • 条件变量:QWaitCondition
  • 信号量:QSemaphore
  • 读写锁:QReadLocker、QWriteLocker、QReadWriteLock 

2.1 互斥锁

互斥锁是一种保护和防止多个线程同时访问同一对象实例的方法,在Qt中,互斥锁主要是通过QMutex类来处理

QMutex

  • 特点:QMutex是 Qt 框架提供的互斥锁类,用于保护共享资源的访问,实现线程间的互斥操作
  • 用途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全
QMutex mutex;
mutex.lock(); //上锁
//访问共享资源
//...
mutex.unlock(); //解锁

QMutexLocker

  • 特点:QMutexLocker是QMutex的辅助类,使用RAII(Resource Acquisition Is Initialization)方式对互斥锁进行上锁和解锁操作
  • 用途:简化对互斥锁的上锁和解锁操作,避免忘记解锁、异常等问题导致的死锁
QMutex mutex;
{
    QMutexLocker locker(&mutex); //在作⽤域内⾃动上锁
    //访问共享资源
    //...
} //在作⽤域结束时⾃动解锁

QReadWriteLocker、QReadLocker、QWriteLocker

特点:

  • QReadWriteLock 是读写锁类,用于控制读和写的并发访问
  • QReadLocker 用于读操作上锁,允许多个线程同时读取共享资源
  • QWriteLocker 用于写操作上锁,只允许一个线程写入共享资源

用途:在某些情况下,多个线程可以同时读取共享数据,但只有一个线程能够进行写操作。读写锁提供了更高效的并发访问方式

QReadWriteLock rwLock; 

//在读操作中使⽤读锁
{
    QReadLocker locker(&rwLock); //在作⽤域内⾃动上读锁
    //读取共享资源
    //...
} //在作⽤域结束时⾃动解读锁

//在写操作中使⽤写锁
{
    QWriteLocker locker(&rwLock); //在作⽤域内⾃动上写锁
    //修改共享资源
    //...
} //在作⽤域结束时⾃动解写锁

代码示例

thread.h

#ifndef THREAD_H
#define THREAD_H

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

class Thread : public QThread
{
    Q_OBJECT
public:
    Thread(QObject* parent = nullptr);
    void run();
private:
    static QMutex mutex; //多个线程使用一把锁
    static int number;	 //多个线程访问同一个数据
};

#endif // THREAD_H

thread.cpp

#include "thread.h"

QMutex Thread::mutex;
int Thread::number;

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

void Thread::run()
{
    while(true)
    {
        mutex.lock();
        qDebug() << "current thread:" << this << ", value:" << number++;
        mutex.unlock();
        QThread::sleep(2);//休眠两秒
    }
}

widget.h 

#ifndef WIDGET_H
#define WIDGET_H

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

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

private:
    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);

    Thread* thread1 = new Thread(this);
    Thread* thread2 = new Thread(this);
    thread1->start();
    thread2->start();
}

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

2.2 条件变量

  • 特点:QWaitCondition是 Qt 框架提供的条件变量类,用于线程之间的消息通信和同步
  • 用途:在某个条件满足时等待或唤醒线程,用于线程的同步和协调
QMutex mutex;
QWaitCondition condition;

//在等待线程中
mutex.lock();
//检查条件是否满⾜,若不满⾜则等待
while (!conditionFullfilled()) {
    condition.wait(&mutex); //等待条件满⾜并释放锁
} 
//条件满⾜后继续执⾏
//...
mutex.unlock();

//在改变条件的线程中
mutex.lock();
//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程
mutex.unlock();

2.3 信号量 

有时在多线程编程中,需要确保多个线程可以相应的访问一个数量有限的相同资源。例如,运行程序的设备内存有限,因此更希望需要大量内存的线程将这一事实考虑在内,并根据可用的内存数量进行相关操作,多线程编程中类似问题通常用信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,还可以跟踪可用资源的数量

  • 特点:QSemaphore是 Qt 框架提供的计数信号量类,用于控制同时访问共享资源的线程数量
  • 用途:限制并发线程数量,用于解决一些资源有限的问题
QSemaphore semaphore(2); //同时允许两个线程访问共享资源

//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞
//访问共享资源
//...
semaphore.release(); //释放信号量
//在另⼀个线程中进⾏类似操作

 

  • 19
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

GG_Bond20

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

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

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

打赏作者

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

抵扣说明:

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

余额充值