您好,登錄后才能下訂單哦!
今天小編給大家分享一下Qt中的線程怎么應用的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
下面,是一個使用多線程操作UI界面的示例 - 更新進度條,采用子類化QThread的方式。與此同時,分享在此過程中有可能遇到的問題及解決方法。
首先創建QtGui應用,工程名稱為“myThreadBar”,類名選擇“QMainWindow”,其他選項保持默認即可。再添加一個名稱為WorkerThread的頭文件,定義一個WorkerThread類,讓其繼承自QThread,并重寫run()函數,修改workerthread.h文件如下:
#ifndef WORKERTHREAD_H #define WORKERTHREAD_H #include <QThread> #include <QDebug> class WorkerThread : public QThread { Q_OBJECT public: explicit WorkerThread(QObject *parent = 0) : QThread(parent) { qDebug() << "Worker Thread : " << QThread::currentThreadId(); } protected: virtual void run() Q_DECL_OVERRIDE { qDebug() << "Worker Run Thread : " << QThread::currentThreadId(); int nValue = 0; while (nValue < 100) { // 休眠50毫秒 msleep(50); ++nValue; // 準備更新 emit resultReady(nValue); } } signals: void resultReady(int value); }; #endif // WORKERTHREAD_H
通過在run()函數中調用msleep(50),線程會每隔50毫秒讓當前的進度值加1,然后發射一個resultReady()信號,其余時間什么都不做。在這段空閑時間,線程不占用任何的系統資源。當休眠時間結束,線程就會獲得CPU時鐘,將繼續執行它的指令。
再在mainwindow.ui上添加一個按鈕和進度條部件,然后mainwindow.h修改如下:
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include "workerthread.h" namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: // 更新進度 void handleResults(int value); // 開啟線程 void startThread(); private: Ui::MainWindow *ui; WorkerThread m_workerThread; }; #endif // MAINWINDOW_H
然后mainwindow.cpp修改如下:
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); qDebug() << "Main Thread : " << QThread::currentThreadId(); // 連接信號槽 this->connect(ui->pushButton, SIGNAL(clicked(bool)), this, SLOT(startThread())); } MainWindow::~MainWindow() { delete ui; } void MainWindow::handleResults(int value) { qDebug() << "Handle Thread : " << QThread::currentThreadId(); ui->progressBar->setValue(value); } void MainWindow::startThread() { WorkerThread *workerThread = new WorkerThread(this); this->connect(workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int))); // 線程結束后,自動銷毀 this->connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater())); workerThread->start(); }
由于信號與槽連接類型默認為“Qt::AutoConnection”,在這里相當于“Qt::QueuedConnection”。也就是說,槽函數在接收者的線程(主線程)中執行。
執行程序,“應用程序輸出”窗口輸出如下:
Main Thread : 0x3140
Worker Thread : 0x3140
Worker Run Thread : 0x2588
Handle Thread : 0x3140
顯然,UI界面、Worker構造函數、槽函數處于同一線程(主線程),而run()函數處于另一線程(次線程)。
當多次點擊“開始”按鈕的時候,就會多次connect(),從而啟動多個線程,同時更新進度條。為了避免這個問題,我們先在mainwindow.h上添加私有成員變量"WorkerThread m_workerThread;",然后修改mainwindow.cpp如下:
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); // 連接信號槽 this->connect(ui->pushButton, SIGNAL(clicked(bool)), this, SLOT(startThread())); this->connect(&m_workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int))); } MainWindow::~MainWindow() { delete ui; } void MainWindow::handleResults(int value) { qDebug() << "Handle Thread : " << QThread::currentThreadId(); ui->progressBar->setValue(value); } void MainWindow::startThread() { if (!m_workerThread.isRunning()) m_workerThread.start(); }
不再在startThread()函數內創建WorkerThread對象指針,而是定義私有成員變量,再將connect添加在構造函數中,保證了信號槽的正常連接。在線程start()之前,可以使用isFinished()和isRunning()來查詢線程的狀態,判斷線程是否正在運行,以確保線程的正常啟動。
如果一個線程運行完成,就會結束。可很多情況并非這么簡單,由于某種特殊原因,當線程還未執行完時,我們就想中止它。
不恰當的中止往往會引起一些未知錯誤。比如:當關閉主界面的時候,很有可能次線程正在運行,這時,就會出現如下提示:
QThread: Destroyed while thread is still running
這是因為次線程還在運行,就結束了UI主線程,導致事件循環結束。這個問題在使用線程的過程中經常遇到,尤其是耗時操作。大多數情況下,當程序退出時,次線程也許會正常退出。這時,雖然抱著僥幸心理,但隱患依然存在,也許在極少數情況下,就會出現Crash。
所以,我們應該采取合理的措施來優雅地結束線程,一般思路:
發起線程退出操作,調用quit()或exit()。
等待線程完全停止,刪除創建在堆上的對象。
適當的使用wait()(用于等待線程的退出)和合理的算法。
方法一
這種方式是Qt4.x中比較常用的,主要是利用“QMutex互斥鎖 + bool成員變量”的方式來保證共享數據的安全性。在workerthread.h上繼續添加互斥鎖、析構函數和stop()函數,修改如下:
#ifndef WORKERTHREAD_H #define WORKERTHREAD_H #include <QThread> #include <QMutexLocker> #include <QDebug> class WorkerThread : public QThread { Q_OBJECT public: explicit WorkerThread(QObject *parent = 0) : QThread(parent), m_bStopped(false) { qDebug() << "Worker Thread : " << QThread::currentThreadId(); } ~WorkerThread() { stop(); quit(); wait(); } void stop() { qDebug() << "Worker Stop Thread : " << QThread::currentThreadId(); QMutexLocker locker(&m_mutex); m_bStopped = true; } protected: virtual void run() Q_DECL_OVERRIDE { qDebug() << "Worker Run Thread : " << QThread::currentThreadId(); int nValue = 0; while (nValue < 100) { // 休眠50毫秒 msleep(50); ++nValue; // 準備更新 emit resultReady(nValue); // 檢測是否停止 { QMutexLocker locker(&m_mutex); if (m_bStopped) break; } // locker超出范圍并釋放互斥鎖 } } signals: void resultReady(int value); private: bool m_bStopped; QMutex m_mutex; }; #endif // WORKERTHREAD_H
當主窗口被關閉,其“子對象”WorkerThread也會析構調用stop()函數,使m_bStopped變為true,則break跳出循環結束run()函數,結束進程。當主線程調用stop()更新m_bStopped的時候,run()函數也極有可能正在訪問它(這時,他們處于不同的線程),所以存在資源競爭,因此需要加鎖,保證共享數據的安全性。
為什么要加鎖?
很簡單,是為了共享數據段操作的互斥。避免形成資源競爭的情況(多個線程有可能訪問同一共享資源的情況)。
方法二
Qt5以后,可以使用requestInterruption()、isInterruptionRequested()這兩個函數,使用很方便,修改workerthread.h文件如下:
#ifndef WORKERTHREAD_H #define WORKERTHREAD_H #include <QThread> #include <QMutexLocker> #include <QDebug> class WorkerThread : public QThread { Q_OBJECT public: explicit WorkerThread(QObject *parent = nullptr) : QThread(parent) { qDebug() << "Worker Thread : " << QThread::currentThreadId(); } ~WorkerThread() { // 請求終止 requestInterruption(); quit(); wait(); } protected: virtual void run() Q_DECL_OVERRIDE { qDebug() << "Worker Run Thread : " << QThread::currentThreadId(); int nValue = 0; // 是否請求終止 while (!isInterruptionRequested()) { while (nValue < 100) { // 休眠50毫秒 msleep(50); ++nValue; // 準備更新 emit resultReady(nValue); } } } signals: void resultReady(int value); }; #endif // WORKERTHREAD_H
在耗時操作中使用isInterruptionRequested()來判斷是否請求終止線程,如果沒有,則一直運行;當希望終止線程的時候,調用requestInterruption()即可。這兩個函數內部也使用了互斥鎖QMutex。
以上就是“Qt中的線程怎么應用”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。