qt 多线程
1. Qt 多线程概述
在 Qt 中,多线程的处理⼀般是通过 QThread 类来实现。QThread 代表⼀个在应用程序中可以独立控制的线程,也可以和进程中的其他线程共享数据。QThread 对象管理程序中的⼀个控制线程。
2. QThread 常用 API
3. 使用线程
创建线程的步骤:
- 自定义一个类,继承于 QThread,并且只有⼀个线程处理函数(和主线程不是同⼀个线程),这个线程处理函数主要就是重写父类中的 run() 函数。
- 线程处理函数里面写入需要执行的复杂数据处理;
- 启动线程不能直接调用 run() 函数,需要使用对象来调用 start() 函数实现线程启动;
- 线程处理函数执行结束后可以定义⼀个信号来告诉主线程;
- 最后关闭线程。
示例代码:
1、首先新建 Qt 项目,设计 UI 界面如下,一个 label 和 PushButton:
2、新建一个类,继承于 QThread 类;
3、timethread.h 的程序如下:
代码语言:javascript复制 #include <QThread>
class TimeThread : public QThread
{
Q_OBJECT
public:
TimeThread();
void run(); // 线程任务函数
signals:
void sendTime(QString Time); // 声明信号函数
};
4、widget.h 程序如下:
代码语言:javascript复制 #include <QWidget>
#include <timethread.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 showTime(QString Time);
void on_pushButton_clicked();
private:
Ui::Widget *ui;
TimeThread t; // 定义线程对象
};
5、timethread.cpp 程序如下:
代码语言:javascript复制 #include "timethread.h"
#include <QTime>
#include <QDebug>
TimeThread::TimeThread()
{}
void TimeThread::run()
{
while(1) {
QString time = QTime::currentTime().toString("hh:mm:ss");
qDebug() << time;
emit sendTime(time); // 发送信号
sleep(1);
}
}
widget.cpp 如下:
代码语言:javascript复制 #include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(&t, &TimeThread::sendTime, this, &Widget::showTime);
}
Widget::~Widget()
{
delete ui;
}
void Widget::showTime(QString Time)
{
ui->label->setText(Time);
}
void Widget::on_pushButton_clicked()
{
t.start(); // 开启线程
}
效果如下:
注意:
- 线程函数内部不允许操作 UI 图形界⾯,⼀般⽤数据处理;
- connect() 函数第五个参数表示的为连接的方式,且只有在多线程的时候才意义。
connect() 函数第五个参数为 Qt::ConnectionType,用于指定信号和槽的连接类型。同时影响信号的传递方式和槽函数的执行顺序。Qt::ConnectionType 提供了以下五种方式:
4. 线程安全
实现线程互斥和同步常用的类有:
- 互斥锁:QMutex、QMutexLocker
- 条件变量:QWaitCondition
- 信号量:QSemaphore
- 读写锁:QReadLocker、QWriteLocker、QReadWriteLock
(1)互斥锁
互斥锁是⼀种保护和防止多个线程同时访问同⼀对象实例的方法,在 Qt 中,互斥锁主要是通过 QMutex 类来处理。
- QMutex
特点:QMutex 是 Qt 框架提供的互斥锁类,用于保护共享资源的访问,实现线程间的互斥操作。
用途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。
代码语言:javascript复制 QMutex mutex;
mutex.lock(); //上锁
//访问共享资源
//...
mutex.unlock(); //解锁
- QMutexLocker
特点:QMutexLocker 是 QMutex 的辅助类,使用 RAII(Resource Acquisition Is Initialization)方式对互斥锁进⾏上锁和解锁操作。
用途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题。
代码语言:javascript复制 QMutex mutex;
{
QMutexLocker locker(&mutex); //在作⽤域内⾃动上锁
//访问共享资源
//...
} //在作⽤域结束时自动解锁
- QReadWriteLocker、QReadLocker、QWriteLocker
特点:
QReadWriteLock 是读写锁类,用于控制读和写的并发访问。 QReadLocker 用于读操作上锁,允许多个线程同时读取共享资源。 QWriteLocker 用于写操作上锁,只允许⼀个线程写⼊共享资源。
用途:在某些情况下,多个线程可以同时读取共享数据,但只有⼀个线程能够进行写操作。读写锁提供了更高效的并发访问方式。
代码语言:javascript复制 QReadWriteLock rwLock;
//在读操作中使⽤读锁
{
QReadLocker locker(&rwLock); //在作⽤域内⾃动上读锁
//读取共享资源
//...
} //在作⽤域结束时⾃动解读锁
//在写操作中使⽤写锁
{
QWriteLocker locker(&rwLock); //在作⽤域内⾃动上写锁
//修改共享资源
//...
} //在作⽤域结束时⾃动解写锁
示例代码:
1、myThread.h 程序如下:
代码语言:javascript复制 #include <QThread>
#include <QMutex>
class myThread : public QThread
{
Q_OBJECT
public:
explicit myThread(QObject* parent = nullptr);
void run();
private:
static QMutex mutex; // 多个线程使用一把锁
static int num; // 多个线程访问一个数据
};
2、myThread.cpp 程序如下:
代码语言:javascript复制 #include "mythread.h"
#include <QDebug>
QMutex myThread::mutex;
int myThread::num = 0;
myThread::myThread(QObject* parent) : QThread(parent)
{}
void myThread::run()
{
while (1) {
this->mutex.lock(); // 加锁
qDebug() << "Current Thread: " << this << ", Value: " << this->num ;
this->mutex.unlock(); // 解锁
QThread::sleep(1); // 线程睡眠两秒
}
}
3、widget.h 程序如下:
代码语言:javascript复制 class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
};
4、widget.cpp 程序如下:
代码语言:javascript复制 #include "widget.h"
#include "ui_widget.h"
#include "mythread.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
myThread* t1 = new myThread(this);
myThread* t2 = new myThread(this);
t1->start();
t2->start();
}
执行效果如下,两个线程使用一把锁,操作⼀个数据,数据会被两个线程依次打印:0、1、2、3、4 …
示例代码2: 在上述示例的基础上使用 QMutexLocker 锁,只修改 myThread.cpp 即可:
代码语言:javascript复制 #include "mythread.h"
#include <QDebug>
QMutex myThread::mutex;
int myThread::num = 0;
myThread::myThread(QObject* parent) : QThread(parent)
{}
void myThread::run()
{
while (1) {
//QMutexLocker:创建的时候加锁,当QMutexLocker局部销毁的时候解锁
{
QMutexLocker lock(&this->mutex);
qDebug() << "Current Thread: " << this << ", Value: " << this->num ;
}
QThread::sleep(1);
}
}
(2)条件变量
在多线程编程中,假设除了等待操作系统正在执行的线程之外,某个线程还必须等待某些条件满足才能执行,这时就会出现问题。这种情况下,线程会很自然地使用锁的机制来阻塞其他线程,因为这只是线程的轮流使用,并且该线程等待某些特定条件,人们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进⼊了睡眠状态,这样其他线程就可以继续运行。当条件满足时,等待条件的线程将被另⼀个线程唤醒。
在 Qt 中,专门提供了 QWaitCondition 类来解决像上述这样的问题。
特点:QWaitCondition 是 Qt 框架提供的条件变量类,用于线程之间的消息通信和同步。
用途:在某个条件满足时等待或唤醒线程,用于线程的同步和协调。
简单的伪代码用法如下:
代码语言:javascript复制 QMutex mutex;
QWaitCondition condition;
//在等待线程中
mutex.lock();
//检查条件是否满⾜,若不满⾜则等待
while (!conditionFullfilled())
{
condition.wait(&mutex); //等待条件满⾜并释放锁
}
//条件满⾜后继续执⾏
//...
mutex.unlock();
//在改变条件的线程中
mutex.lock();
//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程
mutex.unlock();
(3)信号量
有时在多线程编程中,需要确保多个线程可以相应的访问⼀个数量有限的相同资源。例如,运行程序的设备可能是非常有限的内存,因此我们更希望需要大量内存的线程将这一事实考虑在内,并根据可用的内存数量进行相关操作,多线程编程中类似问题通常用信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,而且可以跟踪可用资源的数量。
特点:QSemaphore 是 Qt 框架提供的计数信号量类,用于控制同时访问共享资源的线程数量。
用途:限制并发线程数量,用于解决⼀些资源有限的问题。
代码语言:javascript复制 QSemaphore semaphore(2); //同时允许两个线程访问共享资源
//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞
//访问共享资源
//...
semaphore.release(); //释放信号量
//在另⼀个线程中进⾏类似操作