【Qt】系统相关(3)------多线程
- [1. Qt多线程概念](#1. Qt多线程概念)
- [2. 线程安全](#2. 线程安全)
-
- [2.1 互斥锁](#2.1 互斥锁)
- [2.2 条件变量](#2.2 条件变量)
- [2.3 信号量](#2.3 信号量)
1. Qt多线程概念
首先说明一下,Qt中的多线程和linux中的多线程是一个东西,只不过Qt中对其进行了封装,在Qt中可以更方便的使用。在Qt中,多线程的处理一般是通过QThread类来实现。QThread代表一个在应用程序中可以独立控制的线程,也可以和进程中的其他线程共享数据。QThread对象管理程序中的一个控制线程。
在Qt中想要创建线程,首先就要创建出y8geQThread的实例,并且在创建线程的时候重点要制定线程的入口函数,而这个入口函数是通过创建一个QThread的子类,然后重写其中的run函数,来起到指定入口函数的方式(这里就用到了多态,就有点类似于我们之前学的事件)。
QThtread常用API
| API | 作用 |
|---|---|
| run() | 线程的入口函数 |
| start() | 通过调用ru0开始执行线程。操作系统将根据优先级参数调度线程。如果线程已经在运行,这个函数什么也不做。 |
| currentThread() | 返回一个指向管理当前执行线程的QThread的指针。 |
| isRunning() | 如果线程正在运行则返回true;否则返回false。 |
| sleep()/msleep()/usleep() | 使线程休眠,单位为秒/毫秒/微秒 |
| wait() | 阻塞线程,直到满足以下任何一个条件:与此QThread对象关联的线程已经完成执行(即当它从run0返回时)。如果线程已经完成,这个函数将返回true。如果线程尚未启动,它也返回true。已经过了几毫秒。如果时间是ULONG_MAX(默认值),那么等待永远不会超时(线程必须从run0返回)。如果等待超时,此函数将返回false。这提供了与POSIX pthread_join0函数类似的功能。终止线程的执行。线程可以立即终止,也可以不立即终止,这取决于操作系统的调 |
| terminate() | 度策略。在terminate0之后使用QThread:wait0来确保。 |
| finished() | 当线程结束时会发出该信号,可以通过该信号来实现线程的清理工作。 |
示例:通过创建一个线程来实现一个定时器,在之前的事件章节中是通过QTimer和QTimerEvetn来实现的定时器,而本章节同样可以使用线程的方式来实现。具体步骤就是,创建一个线程类继承自QThread,然后重写run函数,在run函数中实现一个for循环,并且每隔一秒中就发射一个信号,而信号由主线程接收,也就相当于实现了每隔一秒钟的定时器。
- 创建一个继承自QThread的类


thread.h头文件
c
#include <QWidget>
#include <QThread>
class Thread : public QThread
{
Q_OBJECT
public:
Thread();
// 重写run
void run();
signals:
// 自定义信号
void notify();
};
thread.cpp源文件
c
void Thread::run()
{
// 在run中,也就是在线程中不能直接修改界面,而是要主线程才能修改界面
for (int i = 0; i < 10; ++i){
// sleep本身是QThread的成员函数,可以直接调用
sleep(1);
// 发出一个信号
emit notify();
}
}
widget.h头文件
c
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public slots:
void handel();
private:
Ui::Widget *ui;
Thread thread;
};
widget.cpp源文件
c
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 关联线程
connect(&thread, &Thread::notify, this, &Widget::handel);
// 启动线程
thread.start();
}
Widget::~Widget()
{
delete ui;
}
void Widget::handel()
{
int value = ui->lcdNumber->intValue();
--value;
ui->lcdNumber->display(value);
}
2. 线程安全
实现线程互斥和同步常用的类有:
- 互斥锁:QMutex、QMutexLocker
- 条件变量:QWaitCondition
- 信号量:QSemaphore
- 读写锁:QReadLocker、QWriteLocker、QReadWriteLock
2.1 互斥锁
互斥锁是一种保护和防止多个线程同时访问同一对象实例的方法,在Qt中,互斥锁主要是通过QMutex类来处理。
QMutex
特点:QMutex是Qt框架提供的互斥锁类,用于保护共享资源的访问,实现线程间的互斥操作。
用途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。
**示例:**创建两个线程,对临界资源num进行加法操作
thread.h
c
#include <QWidget>
#include <QThread>
#include <QMutex>
class Thread : public QThread
{
Q_OBJECT
public:
Thread();
// 重写run
void run();
// 声明两个静态成员
static int num;
static QMutex mutex;
};
thread.cpp
c
#include "thread.h"
// 定义
int Thread::num = 0;
QMutex Thread::mutex;
Thread::Thread()
{
}
void Thread::run()
{
for (int i = 0; i < 100; ++i){
mutex.lock();
num++;
mutex.unlock();
}
}
widget.cpp
c
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建两个线程
Thread t1;
Thread t2;
// 启动线程
t1.start();
t2.start();
// 线程等待,让主线程等待这两个线程结束,这里需要注意的是,如果没有线程等待的话,启动程序是由三个线程的,一个是主线程,两个创建
// 的线程,而当创建的线程启动的时候,主线程也会接着往下走,那么当执行的qDebug的时候可能两个线程还没有结束,所以打印出的结果
// 就会出错。
t1.wait();
t2.wait();
qDebug() << Thread::num;
}
QMutexLocker
特点:QMutexLocker是QMutex的辅助类,使用RAl(Resource Acquisition Is Initialization)方式对互斥锁进行上锁和解锁操作。
用途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题。
于是上面的代码就可以写成
thread.cpp
c
#include "thread.h"
// 定义
int Thread::num = 0;
QMutex Thread::mutex;
Thread::Thread()
{
}
void Thread::run()
{
for (int i = 0; i < 100; ++i){
QMutexLocker locker(&mutex);
num++;
}
}
QReadWriteLocker、QReadLocker、.QWriteLocker
特点:
- QReadWriteLock是读写锁类,用于控制读和写的并发访问。
- QReadLocker用于读操作上锁,允许多个线程同时读取共享资源。
- QWriteLocker用于写操作上锁,只允许一个线程写入共享资源。
用途:在某些情况下,多个线程可以同时读取共享数据,但只有一个线程能够进行写操作。读写锁提
供了更高效的并发访问方式。
c
QReadWriteLock rwLock;
//在读操作中使⽤读锁
{
QReadLocker locker(&rwLock); //在作⽤域内⾃动上读锁
//读取共享资源
//...
} //在作⽤域结束时⾃动解读锁
//在写操作中使⽤写锁
{
QWriteLocker locker(&rwLock); //在作⽤域内⾃动上写锁
//修改共享资源
//...
} //在作⽤域结束时⾃动解写锁
2.2 条件变量
在多线程编程中,假设除了等待操作系统正在执行的线程之外,某个线程还必须等待某些条件满足才能执行,这时就会出现问题。这种情况下,线程会很自然地使用锁的机制来阻塞其他线程,因为这只是线程的轮流使用,并且该线程等待某些特定条件,人们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进入了睡眠状态,这样其他线程就可以继续运行。当条件满足时,等待条件的线程将被另一个线程唤醒。在Qt中,专门提供了QWaitCondition类来解决像上述这样的问题。
特点 :QWaitCondition是Qt框架提供的条件变量类,用于线程之间的消息通信和同步。
用途:在某个条件满足时等待或唤醒线程,用于线程的同步和协调。
c
QMutex mutex;
QWaitCondition condition;
//在等待线程中
mutex.lock();
//检查条件是否满⾜,若不满⾜则等待
while (!conditionFullfilled())
{
condition.wait(&mutex); //等待条件满⾜并释放锁
}
//条件满⾜后继续执⾏
//...
mutex.unlock();
//在改变条件的线程中
mutex.lock();
//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程
mutex.unlock();
2.3 信号量
有时在多线程编程中,需要确保多个线程可以相应的访问一个数量有限的相同资源。例如,运行程序的设备可能是非常有限的内存,因此我们更希望需要大量内存的线程将这一事实考虑在内,并根据可用的内存数量进行相关操作,多线程编程中类似问题通常用信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,而且可以跟踪可用资源的数量。
特点 :QSemaphore是Qt框架提供的计数信号量类,用于控制同时访问共享资源的线程数量。
用途:限制并发线程数量,用于解决一些资源有限的问题。
c
QSemaphore semaphore(2); //同时允许两个线程访问共享资源
//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞
//访问共享资源
//...
semaphore.release(); //释放信号量
//在另⼀个线程中进⾏类似操作