目录
概述
- 在 Qt 中,多线程的处理⼀般是通过 QThread类 来实现。
- QThread 代表⼀个在应⽤程序中可以独⽴控制的线程,也可以和进程中的其他线程共享数据。
- QThread 对象管理程序中的⼀个控制线程。
QThread要想创建线程,就需要创建出这样类的实例,创建线程的时候,需要重点指定线程的入口函数,创建一个QThread的子类,重写其中的run函数,起到指定入口函数的方式。
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() |
当线程结束时会发出该信号,可以通过该信号来实现线程的清理⼯作。 |
线程使用
由于存在线程安全的问题,多个线程同时对于界面的状态进行修改,此时就会导致界面出错了,因此在qt中,多线程内部不允许对界面的控件状态进行修改,只能在主线程中进行修改。
创建一个QThread的子类
thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QThread>
#include <QWidget>
class Thread : public QThread
{
Q_OBJECT
public:
Thread();
void run();
signals:
void notify();
};
#endif // THREAD_H
thread.cpp
#include "thread.h"
Thread::Thread()
{
}
void Thread::run()
{
for(int i = 0 ; i < 10; i++)
{
sleep(1);
emit notify();
}
}
主线程启动线程
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include "thread.h"
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void handle();
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(&thread,&Thread::notify,this,&Widget::handle);
thread.start();
}
Widget::~Widget()
{
delete ui;
}
void Widget::handle()
{
int value = ui->lcdNumber->intValue();
ui->lcdNumber->display(--value);
}
客户端中的多线程,主要用于通过多线程的方式,执行一些耗时的等待IO的操作,避免主线程被卡死,比如当用户需要上传/下载一个很大的文件,我们可以使用单独的线程,来处理这种密集的IO操作,要挂起也是挂起这个新的线程,主线程负责事件循环,负责处理用户的各种操作。
线程安全
实现线程互斥和同步常⽤的类有:
互斥锁
互斥锁是⼀种保护和防⽌多个线程同时访问同⼀对象实例的⽅法,在 Qt 中,互斥锁主要是通过 QMutex类来处理。QMutex中lock加锁,unlock解锁。
QMutex
特点:QMutex 是 Qt 框架提供的互斥锁类,⽤于保护共享资源的访问,实现线程间的互斥操作。
⽤途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。
使用示例:两个线程一个共享静态变量进行++
线程子类创建
thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QThread>
#include <QWidget>
#include <QMutex>
class Thread : public QThread
{
Q_OBJECT
public:
Thread();
void run();
//共享资源
static int num;
//创建一把锁
QMutex mutex;
signals:
void notify();
};
#endif // THREAD_H
thread.cpp
#include "thread.h"
int Thread::num=0;
Thread::Thread()
{
}
void Thread::run()
{
for(int i = 0 ; i < 10; i++)
{
mutex.lock();
num++;
mutex.unlock();
}
}
主线程调用
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
Thread::num=0;
Thread t1,t2;
t1.start();
t2.start();
//让主线程等待两个线程执行完毕
t1.wait();
t2.wait();
qDebug()<<Thread::num;
}
Widget::~Widget()
{
delete ui;
}
QMutexLocker
上述的代码中,在获取到锁后,执行我们指定的逻辑,一旦发生错误,或者异常将会导致锁未释放,因此我们需要引入 QMutexLocker locker(&mutex);
代码示例:
void Thread::run()
{
for(int i = 0 ; i < 10; i++)
{
QMutexLocker locker(&mutex); //在作⽤域内⾃动上锁
num++;
}//在作⽤域结束时⾃动解锁
}
此外还有QReadWriteLocker、QReadLocker、QWriteLocker
条件变量
在多线程编程中,假设除了等待操作系统正在执⾏的线程之外,某个线程还必须等待某些条件满⾜才能执⾏,这时就会出现问题。这种情况下,线程会很⾃然地使⽤锁的机制来阻塞其他线程,因为这只是线程的轮流使⽤,并且该线程等待某些特定条件,⼈们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进⼊了睡眠状态,这样其他线程就可以继续运⾏。当条件满⾜时,等待条件的线程将被另⼀个线程唤醒。
在 Qt 中,专⻔提供了 QWaitCondition类 来解决像上述这样的问题。
- 特点:QWaitCondition 是 Qt 框架提供的条件变量类,⽤于线程之间的消息通信和同步。
- ⽤途:在某个条件满⾜时等待或唤醒线程,⽤于线程的同步和协调
也就是说多个线程之间的调度是无序的,为了能够一定程度的干预线程之间的执行顺序,需要引入条件变量。
信号量
有时在多线程编程中,需要确保多个线程可以相应的访问⼀个数量有限的相同资源。例如,运⾏程序 的设备可能是⾮常有限的内存,因此我们更希望需要⼤量内存的线程将这⼀事实考虑在内,并根据可 ⽤的内存数量进⾏相关操作,多线程编程中类似问题通常⽤信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,⽽且可以跟踪可⽤资源的数量。
- 特点:QSemaphore 是 Qt 框架提供的计数信号量类,⽤于控制同时访问共享资源的线程数量。
- ⽤途:限制并发线程数量,⽤于解决⼀些资源有限的问题。