主页 > IT业界  > 

Qt互斥锁(QMutex)的使用、QMutexLocker的使用

Qt互斥锁(QMutex)的使用、QMutexLocker的使用

Qt互斥锁【QMutex】的使用、QMutexLocker的使用 基于读写锁(QReadWriteLock)的线程同步Chapter1 Qt互斥锁(QMutex)的使用、QMutexLocker的使用一、QMutexLocker和QMutex实现示例图二、QMutex和QMutexLocker的关系(个人理解)三、QMutex使用和QMutexLocker使用1.QMutex的使用2.QMutexLocker的使用 四、检验QMutexLocker是否将传入的互斥锁锁定1.操作解释2.CMoveFuncClass(使用moveToThread实现,使用QMutexLocker)3.CThread类(继承QThread实现,单纯使用QMutex)4.CMainWindow调用类 总结相关文章 Chapter2 QReadWriteLock读写锁1、读写锁的特性:读共享,写独占。2、读写优先级3、常用函数包含:4、常关联的类包含: Chapter3 QT多线程(二):基于互斥锁与读写锁的线程同步1. 线程同步概念2. 基于互斥量的线程同步2.1 QMutex类2.2 QMutexLocker 类 3. 基于读写锁的线程同步


基于读写锁(QReadWriteLock)的线程同步

使用互斥量时存在一个问题,即每次只能有一个线程获得互斥量的使用权限。如果在一个程序中有多个线程读取某个变量,使用互斥量时必须排队。而实际上若只是读取一个变量,可以让多个线程同时访问,这种情况下使用互斥量就会降低程序的性能。

因此提出了读写锁概念,Qt 提供了读写锁类 QReadWriteLock,它是基于读或写的方式进行代码片段锁定的,在多个线程读写一个共享数据时,使用它可以解决使用互斥量存在的上面所提到的问题。

QReadWriteLock 以 读或写锁定的同步方法允许以读或写的方式保护一段代码,它可以允许多个线程以只读方式同步访问资源,但是只要有一个线程在以写入方式访问资源,其他线程就必须等待,直到写操作结束。

简单总结就是:同一时间,多个线程可以同时读,只有一个线程可以写,读写不能同时进行。

Chapter1 Qt互斥锁(QMutex)的使用、QMutexLocker的使用

原文链接: blog.csdn.net/wj584652425/article/details/123585126

一、QMutexLocker和QMutex实现示例图

下图为检测QMutexLocker是否上锁成功的示例图(两个线程使用同一个QMutex),源码在文章第四节(源码含详细注释)。

下图为不同QMutex运行时的效果(该图表明两个线程无关,并非sleep影响了另一个线程的运行)

二、QMutex和QMutexLocker的关系(个人理解)

互斥锁(QMutex)在使用时需要在进入和结束的时候使用对应的函数锁定和解锁。在简单的程序中还好,但是在结构复杂的程序中因为需要手动锁定和解锁,很容易忽略细节而出现问题,于是为了应对这种情况QMutexLocker便诞生了(为了简化简化互斥锁的锁定和解锁)。 QMutexLocker通常创建为局部变量,QMutexLocker在创建时传入一个并未锁定(若是锁定可用relock重新锁定或unlock解锁)的QMutex指针变量,并且会将QMutex变量锁定,在释放时会将QMutex变量解锁。(QMutexLocker创建时将传入的QMutex锁定,释放时将传入的QMutex解锁)

三、QMutex使用和QMutexLocker使用 1.QMutex的使用 void CThread::run() { //互斥锁锁定 m_mutex->lock(); //输出当前线程的线程ID qDebug() << QThread::currentThreadId(); //互斥锁解锁 m_mutex->unlock(); } 2.QMutexLocker的使用 void CThread::run() { //创建QMutexLocker的局部变量,并将类中互斥锁指针传入(此处互斥锁被locker锁定) QMutexLocker locker(m_mutex); qDebug() << QThread::currentThreadId(); //当locker作用域结束locker将互斥锁解锁 }

通过1、2的代码比较,我们会发现QMutexLocker的代码中没有手动调用锁定和解锁,由此可看出MutexLocker简化了互斥锁的锁定和解锁。

四、检验QMutexLocker是否将传入的互斥锁锁定 1.操作解释

使用两种实现方法完全不同线程测试 两个线程使用同一个互斥锁 一个线程使用QMutexLocker一个线程单纯使用QMutex

2.CMoveFuncClass(使用moveToThread实现,使用QMutexLocker)

CMoveFuncClass.h

#ifndef CMOVEFUNCCLASS_H #define CMOVEFUNCCLASS_H #include <QObject> #include <QMutex> class CMoveFuncClass : public QObject { Q_OBJECT public: explicit CMoveFuncClass(QObject *parent = nullptr); ~CMoveFuncClass(); void setMutex(QMutex *mutex); public slots: void doSomething(); private: QMutex * m_mutex; //定义一个互斥锁变量 }; #endif // CMOVEFUNCCLASS_H

CMoveFuncClass.cpp

#include "CMoveFuncClass.h" #include <QDebug> #include <QThread> CMoveFuncClass::CMoveFuncClass(QObject *parent) : QObject(parent) { } CMoveFuncClass::~CMoveFuncClass() { } void CMoveFuncClass::doSomething() { //创建QMutexLocker的局部变量,并将类中互斥锁指针传入(此处互斥锁被locker锁定) QMutexLocker locker(m_mutex); qDebug() << "我的实现方法为moveToThread" <<"开始3秒睡眠" << "使用QMutexLocker"; qDebug() << "线程ID:" << QThread::currentThreadId(); QThread::sleep(3); //设置线程睡眠3秒(单位为秒) qDebug() << "我的实现方法为moveToThread" <<"线程运行完成,结束睡眠\n\n"; //当locker作用域结束locker将互斥锁解锁 } void CMoveFuncClass::setMutex(QMutex *mutex) { m_mutex = mutex; } 3.CThread类(继承QThread实现,单纯使用QMutex)

CThread.h

#ifndef CTHREAD_H #define CTHREAD_H #include <QObject> #include <QThread> #include <QMutex> #include <QWaitCondition> class CThread : public QThread { Q_OBJECT public: explicit CThread(QObject *parent = nullptr); ~CThread(); void run(); void setMutex(QMutex *mutex); private: QMutex * m_mutex; //定义一个线程锁变量 }; #endif // CTHREAD_H

CThread.cpp

#include "CThread.h" #include <QDebug> CThread::CThread(QObject *parent) : QThread(parent) { } CThread::~CThread() { } void CThread::run() { //互斥锁上锁 m_mutex->lock(); qDebug() << "我的实现方法为继承QThread" << "开始3秒睡眠" << "单纯使用QMutex"; qDebug() << "线程ID:" << QThread::currentThreadId(); QThread::sleep(3); //设置线程睡眠3秒(单位为秒) qDebug() << "我的实现方法为继承QThread" <<"线程运行完成,结束睡眠"; //互斥锁解锁 m_mutex->unlock(); } void CThread::setMutex(QMutex *mutex) { m_mutex = mutex; } 4.CMainWindow调用类

CMainWindow.h

#ifndef CMAINWINDOW_H #define CMAINWINDOW_H #include <QMainWindow> #include "CThread.h" #include "CMoveFuncClass.h" namespace Ui { class CMainWindow; } class CMainWindow : public QMainWindow { Q_OBJECT public: explicit CMainWindow(QWidget *parent = 0); ~CMainWindow(); signals: void startMoveThread(); private slots: void on_startBtn_clicked(); //触发方法二函数的信号 private: Ui::CMainWindow *ui; CThread *m_cThread; //方法一指针 CMoveFuncClass *m_moveFunc; //方法二指针 QThread *m_thread; //方法二所移至的线程指针 QMutex *m_mutex; //两个线程使用的线程锁 }; #endif // CMAINWINDOW_H

CMainWindow.cpp

#include "CMainWindow.h" #include "ui_CMainWindow.h" #include <QDebug> CMainWindow::CMainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::CMainWindow) { ui->setupUi(this); /方法一/// //new出CThread对象 m_cThread = new CThread; /方法二/// //new一个moveToThread的接收线程并启动 m_thread = new QThread; //new出CMoveFuncClass对象 m_thread->start(); //一定记得启动,否则运行不了 m_moveFunc = new CMoveFuncClass; //连接相应信号槽 connect(this, &CMainWindow::startMoveThread, m_moveFunc, &CMoveFuncClass::doSomething); connect(m_thread, &QThread::finished, m_moveFunc, &QObject::deleteLater); //将对象移至线程 m_moveFunc->moveToThread(m_thread); //创建线程共用的互斥锁 m_mutex = new QMutex; //下方为m_mutex的地方更改为new QMutex,则能实现第一节,第二张图的效果 m_cThread->setMutex(m_mutex); m_moveFunc->setMutex(m_mutex); } CMainWindow::~CMainWindow() { delete m_mutex; delete m_moveFunc; m_thread->exit(); m_thread->wait(1); delete m_thread; m_cThread->exit(); m_cThread->wait(1); delete m_cThread; delete ui; } void CMainWindow::on_startBtn_clicked() { //通过start启动方法一线程 m_cThread->start(); //发送信号启动方法二线程 emit startMoveThread(); }

运行上方的代码(第一节,第一张效果图)可看出,使用QMutexLocker的线程首先运行,且代码中无锁定和解锁的操作,但另外一个线程依然等该线程运行完成后运行,由此可看出,使用QMutexLocker是实现了互斥锁的锁定和解锁的。

总结

QMutexLocker提供的简化互斥锁锁定和解锁的机制在很多时候时蛮方便的,在使用互斥锁的地方使用QMutexLocker会减去许多安全隐患;不过在多线程循环输出ABC的时候好像就不适合该方法。所以使用类似的类还得按情况而定

相关文章

启动QThread线程的两种方法(含源码+注释) Qt互斥锁(QMutex)、条件变量(QWaitCondition)讲解+QMutex实现多线程循环输出ABC(含源码+注释) QSemaphore的使用+QSemaphore实现循环输出ABC(含源码+注释) QRunnable线程、QThreadPool(线程池)的使用(含源码+注释) Qt读写锁(QReadWriteLock)的使用、读写锁的验证(含源码+注释) Qt读写锁(QWriteLocker、QReadLocker)的理解和使用(含部分源码) Qt之线程运行指定函数(含源码+注释,优化速率)

友情提示——哪里看不懂可私哦,让我们一起互相进步吧 (创作不易,请留下一个免费的赞叭 谢谢 o/)

注:文章为作者编程过程中所遇到的问题和总结,内容仅供参考,若有错误欢迎指出。 注:如有侵权,请联系作者删除

Chapter2 QReadWriteLock读写锁

原文链接: blog.csdn.net/weixin_43246170/article/details/121015495

QT中线程间的同步分别有QMutex互斥锁、QSemephone信号量、QWaitCondition条件变量和QReadWriteLock读写锁四种方式。

这边来介绍的是读写锁,一般应用与具有大量读操作的场景。

1、读写锁的特性:读共享,写独占。

读共享 : 当其他线程占用读锁的时候,如果其他线程请求读锁,会立即获得。 当其他线程占用读锁的时候,如果其他线程请求写锁,会阻塞等待读锁的释放。 写独占 : 当其他线程占用写锁的时候,如果其他线程请求读锁,会阻塞等待写锁的释放。 当其他线程占用写锁的时候,如果其他线程请求写锁,会阻塞等待写锁的释放。

2、读写优先级

默认优先级是写优先,即写锁的优先级>读锁,哪怕是读先排队的也没用。

3、常用函数包含:

lockForRead() ; 请求读锁 lockForWrite() ; 请求写锁 tryLockForRead() ; 尝试请求读锁,非阻塞函数,可以设置超时时间。 tryLockForWrite() ; 尝试请求写锁,非阻塞函数,可以设置超时时间。 unlock() ; 解锁(解读锁和解写锁,均使用该函数)

4、常关联的类包含:

QReadLocker; QWriteLocker;

Chapter3 QT多线程(二):基于互斥锁与读写锁的线程同步

原文链接: blog.csdn.net/qq_46144191/article/details/144494017

此处需要说明的是,这里的线程同步概念与操作系统中的线程同步并无区别,都是避免多个线程同时访问临界区数据可能产生的读写错误问题。在 Qt 中,有多个类可以实现线程同步的功能,这些类包括 QMutex、QMutexLocker、 QReadWriteLock、QReadLocker、QWriteLocker、QWaitCondition、QSemaphore 等。

1. 线程同步概念

在多线程程序中,由于存在多个线程,线程之间可能需要访问同一个变量,或一个线程需要等待另一个线程完成某个操作后才产生相应的动作。例如在上一节中提到的例程,工作线程生成随机的骰子点数,主线程读取骰子点数并显示,主线程需要等待工作线程生成一新的骰子点数后再读取数据。但上一节中并没有使用线程同步机制,而是使用了信号与槽的机制,在生成新的点数之后通过

信号通知主线程读取新的数据。这个过程类似于操作系统的线程信号机制,都是为信号设定一个处理函数,本章中不使用信号与槽函数来讲解其他方式的线程同步方法。

2. 基于互斥量的线程同步

QMutex 和 QMutexLocker 是基于互斥量(mutex)的线程同步类。

2.1 QMutex类

该类提供的API函数如下:

void QMutex::lock() //锁定互斥量,一直等待 void QMutex::unlock() //解锁互斥量 bool QMutex::tryLock() //尝试锁定互斥量,不等待 bool QMutex::tryLock(int timeout) //尝试锁定互斥量,最多等待 timeout 毫秒

函数 lock()锁定互斥量,如果另一个线 程锁定了这个互斥量,它将被阻塞运行直到 其他线程解锁这个互斥量。函数 unlock()解锁互斥量,需要与 lock()配对使用。

函数 tryLock()尝试锁定一个互斥量,如果成功锁定就返回 true,如果其他线程已经锁定了这个互斥量就返回 false。函数 tryLock(int timeout)尝试锁定一个互斥量,如果这个互斥量被其他线程锁定,最多等待 timeout 毫秒。

互斥量相当于一把钥匙,如果两个线程要访问同一个共享资源,就需要通过 lock()或 tryLock()拿到这把钥匙,然后才可以访问该共享资源,访问完之后还要通过unlock()还回钥匙,这样别的线程才有机会拿到钥匙。

在上一节的例程中,在TDiceThread类中添加一个QMutex变量,并删除自定义信号newValue(),增加一个readValue()函数用于提供给主窗口访问类变量。

QMutex mutex; //互斥量 bool TDiceThread::readValue(int *seq, int *diceValue) { if (mutex.tryLock(100)) //尝试锁定互斥量,等待100ms { *seq=m_seq; *diceValue=m_diceValue; mutex.unlock(); //解锁互斥量 return true; } else return false; }

主函数(主线程)在访问m_seq和m_diceValue变量时,会尝试获取“钥匙”,最多等待100ms,得到权限后会通过指针类变量返回值。事后解锁以便于工作线程对这两个变量进行修改。

另一处需修改的是工作线程中的访问,代码如下:

void TDiceThread::run() {//线程的事件循环 m_stop=false; //启动线程时令m_stop=false m_paused=true; //启动运行后暂时不掷骰子 m_seq=0; //掷骰子次数 while(!m_stop) //循环主体 { if (!m_paused) { mutex.lock(); //锁定互斥量 m_diceValue=0; for(int i=0; i<5; i++) m_diceValue += QRandomGenerator::global()->bounded(1,7); //产生随机数[1,6] m_diceValue =m_diceValue/5; m_seq++; mutex.unlock(); //解锁互斥量 } msleep(500); //线程休眠500ms } quit(); //在 m_stop==true时结束线程任务 }

在函数 run()中,我们对重新计算变量 m_diceValue 和 m_seq 值的代码片段用互斥量 mutex 进行了保护。工作线程运行后,其内部的函数 run()一直在运行。主线程里调用工作线程的 readValue()函数,其实际是在主线程里运行的。

通过上述方式,主线程和工作线程都对临界区的变量进行了互斥访问,这样就可确保数据的完整性。

2.2 QMutexLocker 类

QMutexLocker 是另一个简化了互斥量处理的类。QMutexLocker 的构造函数接受互斥量作为参数并将其锁定,QMutexLocker 的析构函数则将此互斥量解锁,所以在 QMutexLocker 实例变量的生存期内的代码片段会得到保护,自动进行互斥量的锁定和解锁。

void TDiceThread::run() {//线程的事件循环 m_stop=false; //启动线程时令m_stop=false m_paused=true; //启动运行后暂时不掷骰子 m_seq=0; //掷骰子次数 while(!m_stop) //循环主体 { if (!m_paused) { QMutexLocker locker(&mutex); m_diceValue=0; for(int i=0; i<5; i++) m_diceValue += QRandomGenerator::global()->bounded(1,7); //产生随机数[1,6] m_diceValue =m_diceValue/5; m_seq++; } msleep(500); //线程休眠500ms } quit(); //在 m_stop==true时结束线程任务 }

这两种实现互斥访问的功能一样,使用是分别注意其形式即可。

在主窗口类的构造函数中,设置了一个定时器,每隔一段时间读取一次临界区变量,如果成功获取到锁并且数据也是最新的,则据此更新主界面。

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); threadA= new TDiceThread(this); connect(threadA,&TDiceThread::started, this, &MainWindow::do_threadA_started); connect(threadA,&TDiceThread::finished,this, &MainWindow::do_threadA_finished); timer= new QTimer(this); //创建定时器 timer->setInterval(200); timer->stop(); connect(timer,&QTimer::timeout, this, &MainWindow::do_timeOut); } void MainWindow::do_timeOut() { int tmpSeq=0,tmpValue=0; bool valid=threadA->readValue(&tmpSeq,&tmpValue); //读取数值 if (valid && (tmpSeq != m_seq)) //有效,并且是新数据 { m_seq=tmpSeq; m_diceValue=tmpValue; QString str=QString::asprintf("第 %d 次掷骰子,点数为:%d",m_seq,m_diceValue); ui->plainTextEdit->appendPlainText(str); QString filename=QString::asprintf(":/dice/images/d%d.jpg",m_diceValue); QPixmap pic(filename); ui->labPic->setPixmap(pic); } }

在开始和结束按钮槽函数中,需要设置定时器的启动和停止。代码如下:

void MainWindow::on_actDice_Run_triggered() {//"开始"按钮,开始掷骰子 threadA->diceBegin(); timer->start(); //重启定时器 ui->actDice_Run->setEnabled(false); ui->actDice_Pause->setEnabled(true); } void MainWindow::on_actDice_Pause_triggered() {//"暂停"按钮,暂停掷骰子 threadA->dicePause(); timer->stop(); //停止定时器 ui->actDice_Run->setEnabled(true); ui->actDice_Pause->setEnabled(false); } 3. 基于读写锁的线程同步

使用互斥量时存在一个问题,即每次只能有一个线程获得互斥量的使用权限。如果在一个程序中有多个线程读取某个变量,使用互斥量时必须排队。而实际上若只是读取一个变量,可以让多个线程同时访问,这种情况下使用互斥量就会降低程序的性能。

因此提出了读写锁概念,Qt 提供了读写锁类 QReadWriteLock,它是基于读或写的方式进行代码片段锁定的,在多个线程读写一个共享数据时,使用它可以解决使用互斥量存在的上面所提到的问题。

QReadWriteLock 以 读或写锁定的同步方法允许以读或写的方式保护一段代码,它可以允许多个线程以只读方式同步访问资源,但是只要有一个线程在以写入方式访问资源,其他线程就必须等待,直到写操作结束。

简单总结就是:同一时间,多个线程可以同时读,只有一个线程可以写,读写不能同时进行。

QReadWriteLock类 提供以下几个主要的函数:

void lockForRead() //以只读方式锁定资源,如果有其他线程以写入方式锁定资源,这个函数会被阻塞 void lockForWrite() //以写入方式锁定资源,如果其他线程以读或写方式锁定资源,这个函数会被阻塞 void unlock() //解锁 bool tryLockForRead() //尝试以只读方式锁定资源,不等待 bool tryLockForRead(int timeout) //尝试以只读方式锁定资源,最多等待 timeout 毫秒 bool tryLockForWrite() //尝试以写入方式锁定资源,不等待 bool tryLockForWrite(int timeout) //尝试以写入方式锁定资源,最多等待 timeout 毫秒

例如下列案例:

int buffer[100]; QReadWriteLock Lock; //定义读写锁变量 void ThreadDAQ::run() //负责采集数据的线程 { ... Lock.lockForWrite(); //以写入方式锁定 get_data_and_write_in_buffer(); //数据写入 buffer Lock.unlock(); ... } void ThreadShow::run() //负责显示数据的线程 { ... Lock.lockForRead(); //以读取方式锁定 show_buffer(); //读取 buffer 里的数据并显示 Lock.unlock(); ... } void ThreadSaveFile::run() //负责保存数据的线程 { ... Lock.lockForRead(); //以读取方式锁定 save_buffer_toFile(); //读取 buffer 里的数据并保存到文件 Lock.unlock(); ... }

另外,QReadLocker 和 QWriteLocker 是 QReadWriteLock 的简便形式,如同 QMutexLocker 是 QMutex 的简便形式一样,无须与 unlock()配对使用。

标签:

Qt互斥锁(QMutex)的使用、QMutexLocker的使用由讯客互联IT业界栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Qt互斥锁(QMutex)的使用、QMutexLocker的使用

上一篇
C++二分图

下一篇
C++核心编程之STL