Qt (19)【Qt 线程安全 | 互斥锁QMutex QMutexLocker | 条件变量 | 信号量】
阅读导航
- 引言
- 一、互斥锁
- 1. QMutex
- (1)基本概念
- (2)使用示例
- 基本需求
- ⭕thread.h
- ⭕thread.cpp
- ⭕widget.h
- ⭕widget.cpp
- 2. QMutexLocker
- (1)基本概念
- (2)使用示例
- 3. QReadWriteLocker、QReadLocker、QWriteLocker
- (1)基本概念
- (2)使用示例
- 二、条件变量
- 1. 基本概念
- 2. 使用示例
- 三、信号量
- 1. 基本概念
- 2. 使用示例
引言
在Qt中,为了构建高效且安全的多线程应用程序,我们需要关注线程安全问题。Qt提供了几种关键的同步机制,包括互斥锁(QMutex)及其自动管理助手QMutexLocker,用于确保资源互斥访问;条件变量,用于线程间的协调与等待/唤醒机制;以及信号量,用于控制对资源的并发访问数。这些工具共同协作,帮助开发者避免数据竞争和死锁,实现线程间的安全交互。
(PS:Linux有详细讲过这方面的知识,传送门:💻【探索Linux】P.21多线程 | 线程同步 | 条件变量 | 线程安全)
一、互斥锁
⭕互斥锁是⼀种保护和防止多个线程同时访问同⼀对象实例的方法,在Qt中,互斥锁主要是通过QMutex
类来处理。
1. QMutex
(1)基本概念
QMutex
是 Qt 框架中一个重要的类,它实现了互斥锁的功能。主要用途是在多线程程序中保护对共享资源的访问,通过锁定和解锁机制来确保同一时间只有一个线程能够访问特定的数据或执行某段代码,从而避免数据竞争和不一致的问题,实现线程间的互斥操作,保证程序的线程安全性。
(2)使用示例
基本需求
使用两个线程对同一个全局变量进行加加,每个线程加5000次
⭕thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QWidget>
#include <QThread>
#include <QMutex>
class Thread : public QThread
{
Q_OBJECT
public:
Thread();
// 添加一个 static 成员.
static int num;
// 创建锁对象
static QMutex mutex;
void run();
};
#endif // THREAD_H
⭕thread.cpp
#include "thread.h"
#include <QMutexLocker>
int Thread::num = 0;
QMutex Thread::mutex;
Thread::Thread()
{
}
void Thread::run()
{
for (int i = 0; i < 50000; i++) {
mutex.lock();
num++;
mutex.unlock();
}
}
⭕widget.h
#ifndef WIDGET_H
#define WIDGET_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();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
⭕widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "thread.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建两个线程对象.
Thread t1;
Thread t2;
t1.start();
t2.start();
// 加上线程的等待, 让主线程等待这俩线程执行结束.
t1.wait();
t2.wait();
// 打印结果.
qDebug() << Thread::num;
}
Widget::~Widget()
{
delete ui;
}
2. QMutexLocker
(1)基本概念
QMutexLocker
是 QMutex
的一个辅助类,它采用了资源获取即初始化(RAII, Resource Acquisition Is Initialization)的设计模式。这一模式确保了互斥锁(QMutex
)在 QMutexLocker
对象的生命周期内被自动上锁,并在该对象被销毁时自动解锁。通过这种方式,QMutexLocker
极大地简化了对互斥锁的管理,开发者无需手动调用上锁(lock)和解锁(unlock)函数,从而避免了因忘记解锁而导致的死锁等潜在问题。
(2)使用示例
上面的QMutex
的示例代码只用进行图片中的变换就可以使用QMutexLocker
了。
3. QReadWriteLocker、QReadLocker、QWriteLocker
(1)基本概念
QReadWriteLock
是一个读写锁类,它设计用于精细控制对共享资源的并发访问权限,特别是在处理读写操作时。在这个锁机制中,区分了读操作和写操作的不同需求。
-
QReadLocker
是用于读操作的上锁工具。当多个线程需要同时读取共享资源时,QReadLocker
允许这些线程同时获得读锁,从而并行访问资源。这种方式提高了读取操作的并发效率,因为读取通常不会修改数据,因此多个读者可以同时安全地访问资源。 -
QWriteLocker
则是用于写操作的上锁工具。与读操作不同,写操作会修改共享资源,因此QWriteLocker
确保在任何给定时间内,只有一个线程能够持有写锁,从而独占对共享资源的写入权限。这保证了数据的一致性和完整性,避免了并发写入可能导致的冲突。
(2)使用示例
QReadWriteLock rwLock;
//在读操作中使⽤读锁
{
QReadLocker locker(&rwLock); //在作⽤域内⾃动上读锁
//读取共享资源
//...
} //在作⽤域结束时⾃动解读锁
//在写操作中使⽤写锁
{
QWriteLocker locker(&rwLock); //在作⽤域内⾃动上写锁
//修改共享资源
//...
} //在作⽤域结束时⾃动解写锁
二、条件变量
1. 基本概念
在多线程编程中,有时线程需要等待某个条件成立才能继续执行。Qt提供了QWaitCondition
类来处理这种情况。QWaitCondition
允许线程在条件不满足时暂停(等待),并在条件满足时被其他线程唤醒。这样,线程可以基于条件进行同步和协调,而不仅仅是简单地轮流使用资源。
2. 使用示例
QMutex mutex;
QWaitCondition condition;
//在等待线程中
mutex.lock();
//检查条件是否满⾜,若不满⾜则等待
while (!conditionFullfilled())
{
condition.wait(&mutex); //等待条件满⾜并释放锁
}
//条件满⾜后继续执⾏
//...
mutex.unlock();
//在改变条件的线程中
mutex.lock();
//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程
mutex.unlock();
三、信号量
1. 基本概念
在多线程编程中,如果多个线程需要访问有限的资源(如内存),就需要控制同时访问这些资源的线程数量。Qt中的QSemaphore
就是一个工具,它可以限制同时运行的线程数,从而管理资源的使用。简单来说,QSemaphore
就像一个计数器,用于确保不会有太多线程同时占用资源。
2. 使用示例
QSemaphore semaphore(2); //同时允许两个线程访问共享资源
//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞
//访问共享资源
//...
semaphore.release(); //释放信号量
//在另⼀个线程中进⾏类似操作