> 作者:დ旧言~
> 座右铭:松树千年终是朽,槿花一日自为荣。> 目标:了解QT中的多线程。
> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!
> 专栏选自:QT从基础到入门_დ旧言~的博客-CSDN博客
> 望小伙伴们点赞👍收藏✨加关注哟💕💕
一、常用API
创建线程的步骤:
- 自定义一个类,继承于QThread,并且只有一个线程处理函数(和主线程不是同一个线程),这个线程处理函数主要就是重写父类中的 run()函数
- 线程处理函数里面写入需要执行的复杂数据处理
- 启动线程不能直接调用 run() 函数,需要使用对象来调用 start() 函数实现线程启动
- 线程处理函数执行结束后可以定义一个信号来告诉主线程
- 最后关闭线程
代码示例:
新建 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(); //释放信号量
//在另⼀个线程中进⾏类似操作
三、结束语
今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。