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的使用
原文鏈接:https://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();//輸出當前線程的線程IDqDebug() << 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_OBJECTpublic: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讀寫鎖
原文鏈接:https://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多線程(二):基于互斥鎖與讀寫鎖的線程同步
原文鏈接:https://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;}elsereturn false;
}
主函數(主線程)在訪問m_seq和m_diceValue變量時,會嘗試獲取“鑰匙”,最多等待100ms,得到權限后會通過指針類變量返回值。事后解鎖以便于工作線程對這兩個變量進行修改。
另一處需修改的是工作線程中的訪問,代碼如下:
void TDiceThread::run()
{//線程的事件循環m_stop=false; //啟動線程時令m_stop=falsem_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=falsem_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()配對使用。