Qt中线程使用汇总
?
QThread類提供一種獨立于平臺的線程管理方式。
方法1
?
一個QThread實例管理程序中的一個線程。QThread的執行開始于run()。默認情況下,run()通過調用exec()啟動事件循環,并在線程內運行Qt事件循環。 ??你可以使用QObject::moveToThread()將工作對象移動到線程中使用。
- 示例:
??將線程移動到工作線程內執行。因為在線程中有隊列的信號槽連接機制,所以在不同線程中使用信號槽是安全的。
方法2
??另一種單獨在線程中執行的方式是繼承QThread后重新實現run()函數(run函數內用戶的執行操作)。
- 示例:
Qt線程—QThread的使用--run和movetoThread的用法
Qt使用線程主要有兩種方法:
方法一:繼承QThread,重寫run()的方法
QThread是一個非常便利的跨平臺的對平臺原生線程的抽象。啟動一個線程是很簡單的。讓我們看一個簡短的代碼:生成一個在線程內輸出"hello"并退出的線程。
// hellothread/hellothread.hclass HelloThread : public QThread{Q_OBJECTprivate:void run();};我們從QThread派生出一個類,并重新實現run方法。
// hellothread/hellothread.cppvoid HelloThread::run(){qDebug() << "hello from worker thread " << thread()->currentThreadId();}run方法中包含將在另一個線程中運行的代碼。在本例中,一個包含線程ID的消息被打印出來。 QThread::start()將在另一個線程中被調用。
int main(int argc, char *argv[]){QCoreApplication app(argc, argv);HelloThread thread;thread.start();qDebug() << "hello from GUI thread " << app.thread()->currentThreadId();thread.wait(); // do not exit before the thread is completed!return 0;}另一種方法:moveToThread的方法
其實,這個方法太簡單,太好用了。定義一個普通的QObject派生類,然后將其對象move到QThread中。使用信號和槽時根本不用考慮多線程的存在。也不用使用QMutex來進行同步,Qt的事件循環會自己自動處理好這個。
/*! * \file main.cpp * * Copyright (C) 2010, dbzhang800 * All rights reserved. * */#include <QtCore/QCoreApplication> #include <QtCore/QObject> #include <QtCore/QThread> #include <QtCore/QDebug> class Dummy:public QObject { Q_OBJECT public: Dummy(QObject* parent=0):QObject(parent) {} public slots: void emitsig() { emit sig(); } signals: void sig(); }; class Object:public QObject { Q_OBJECT public: Object(){} public slots: void slot() { qDebug()<<"from thread slot:" <<QThread::currentThreadId(); } }; #include "main.moc" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug()<<"main thread:"<<QThread::currentThreadId(); QThread thread; Object obj; Dummy dummy; obj.moveToThread(&thread); QObject::connect(&dummy, SIGNAL(sig()), &obj, SLOT(slot())); thread.start(); dummy.emitsig(); return a.exec(); }運行結果,slot不在主線程
main thread: 0x1a5c from thread slot: 0x186c其基本用法包含下面幾步:
QThread *thread = new QThread;threads *thread_slot = new threads;//移動到另一線程thread_slot->moveToThread(thread);thread->start();//兩個類之間的相互通信QObject::connect(this, SIGNAL(trans_signal()), thread_slot, SLOT(th_trans_code()));為什么用線程
在使用QT做窗體程序時有一些占用時間較長的函數在運行時會使QT的窗體控件無法得到響應,也就是常說的程序假死,其實程序還是在運行的,只是你得不到反饋而已,這種情況,可以使用線程來把運行時間長的函數與窗體主線成分開了,當然也可以使用QTimer來執行這一個操作,但QTimer與線程相比有些缺陷,當然,使用線程也有一些缺陷,這些后邊的文章再說明。
線程類的創建
QT線程類有兩種創建方式,一種是繼承QThread的方式,第二種是繼承QObjec的方式,本文是基于第二種方式來創建線程的(QT推薦的方式)以下是線程類的代碼:
class testThread : public QObject { Q_OBJECTpublic:explicit testThread(QObject *parent = nullptr); signals:void send_msg(const QString& msg);public slots:void ChildThread(); // 子線程主循環void Stop(); // 改變事件循環的狀態(停止)void Start(); // 改變事件循環的狀態(開始)private:volatile bool isStop = false; // 改變事件循環的標志位int n = 0; }; void testThread::ChildThread() {while(!isStop) { // 判斷是否停止循環QString msg = "child: " + QString::number(n++);emit send_msg(msg); // 發送信號,窗體顯示信息QThread::msleep(50); // 輸出一次消息休息50ms}qDebug() << "child Thread: " << QThread::currentThread(); } void testThread::Start() {isStop = false; } void testThread::Stop() {isStop = true; }使用線程
QObject類的方法要想在線程中執行,還涉及到了另一個知識即connect的鏈接方式,在我理解中,因為connect的存在而使得使用QT的線程非常方便 ,通過不同的連接方式可以很方便的判斷出該類的曹函數是在哪一個線程中執行的,先把代碼貼上,看一下怎么使用線程,具體的看代碼注釋:
// mainwindow的頭文件,可掠過這段代碼 class MainWindow : public QMainWindow {Q_OBJECTpublic:explicit MainWindow(QWidget *parent = 0);~MainWindow();signals:void Stop();void Start();private:Ui::MainWindow *ui;QTimer timer;QThread *qThread; // 聲明一個QThread線程管理類用來管理testThreadtestThread *childThread; // 聲明一個testThread類,把它扔到線程中int n = 0;private slots:void bt_start();void bt_stop();void receive_msg(const QString& msg); // 接收子線程函數發來的信號并把信息顯示在對話框中void mainMsg(); // 主線程顯示的信息 };以下代碼是本文重點了,講述了如何把一個我們建立的testThread類的函數放入線程中執行
// 重點 void MainWindow::bt_start() {if( !timer.isActive() ) {timer.setInterval(50);timer.start();}if( childThread == NULL ) { // 判斷對象有無申請空間, 如果已經創建了對象,則childThread = new testThread(); // 不再重復創建qThread = new QThread(); //childThread->moveToThread(qThread); // 將testThread類丟入QThread中進行管理(重要)}connect(this, &MainWindow::Stop, childThread, &testThread::\Stop, Qt::DirectConnection); // 第五個參數很重要,決定了曹函數在哪個線程執行connect(childThread, &testThread::send_msg, this, &MainWindow::\receive_msg);connect(&timer, &QTimer::timeout, this, &MainWindow::\mainMsg);connect(this, &MainWindow::Start, childThread, &testThread::\ChildThread); // 連接信號,開啟線程的主循環函數connect(this, &MainWindow::Start, childThread, &testThread::\Start); // 改變子線程循環標志位if( !qThread->isRunning() ) {qThread->start(); // 開啟管理線程,加入判斷是為了避免線程的重復開啟}emit Start(); // 發送開啟信號}在上面的代碼中打了注釋的地方描述了Qt中一個線程創建的過程:
childThread = new testThread();
qThread = new QThread();
childThread->moveToThread(qThread);
qThread->start();
到現在,線程已經可以運行了,下面則是停止線程了,在本文中我說的停止線程并沒有將線程關閉,而是類似于掛起的狀態,等到再次發送信號時,線程的執行函數還將繼續運行且之前的資源并沒有被釋放掉,在C語言中線程執行的函數如果執行結束,如果線程的引用數為0那么該線程也將關閉,并且除了小部分資源(例如返回碼的所占的內存)其他用戶自定義的線程的資源將被釋放,在QT中因為封裝好了線程類,所以我們看不到這里面的狀態,第二篇文章我會說一下該怎么在QT中正確的關閉線程。
void MainWindow::bt_stop() {if( timer.isActive() ) {timer.stop();}emit Stop(); // 發送停止信號,改變循環標志位disconnect(childThread, &testThread::send_msg, this, &MainWindow::\receive_msg); // 斷開線程循環函數中的信號連接disconnect(&timer, &QTimer::timeout, this, &MainWindow::\mainMsg); disconnect(this, &MainWindow::Start, childThread, &testThread::\ChildThread); // 斷開執行函數信號的連接disconnect(this, &MainWindow::Start, childThread, &testThread::\Start); // 斷開開始標志位信號的連接qDebug() << "main thread: " << QThread::currentThread(); // 顯示當前線程 }下面是運行程序時顯示的畫面
圖中可以看到,程序運行之后輸出的信息是在不同線程中的,至此,本文結束,有疑問的朋友可以私信評論,有什么說得不到位的或者錯誤的也可以提出來
Qt多線程中的信號與槽
1. Qt對象的依附性和事務循環
??QThread繼承自QObject,自然擁有發射信號/定義槽函數的能力。QThread默認聲明了以下幾個關鍵信號(信號只能聲明不能定義):
??(1) 線程開始運行時發射的信號
??(2) 線程完成運行時發射的信號
void finished()- ?
??(3) 線程被異常終止時發射的信號
void terminated()??多線程中的信號與槽之間的關系,是Qt編程中一個相對復雜的內容。先看下面例子:
//TestThread.h聲明了線程類 #ifndef TESTTHREAD_H #define TESTTHREAD_H#include <QThread> #include <QObject> #include <QDebug>//在線程類TestThread中自定義信號和槽函數 class TestThread : public QThread {Q_OBJECTprotected:void run(); //線程執行函數public:explicit TestThread(QObject *parent = 0);signals:void TestThread_Signal(); //自定義的信號public slots:void TestThread_Slot(); //對應TestThread_Signal()信號的槽函數 };#endif // TESTTHREAD_H//TestThread.cpp #include "TestThread.h" #include <QDebug>TestThread::TestThread(QObject *parent) :QThread(parent) {//連接信號與槽connect(this, SIGNAL(TestThread_Signal()), this, SLOT(TestThread_Slot())); }//子線程執行函數 void TestThread::run() {//打印新線程的ID值qDebug() << "void TestThread::run(): tid = " << currentThreadId();//發射自定義信號emit TestThread_Signal(); }void TestThread::TestThread_Slot() {//打印此函數運行時的所在線程的ID值qDebug() << "TestThread::TestThread_Slot(): tid = " << currentThreadId(); }//main.c #include <QtCore/QCoreApplication> #include "TestThread.h" #include "MyObject.h"int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);qDebug() << "main: tid = " << QThread::currentThreadId();TestThread t;//開啟線程執行函數,run()得到執行t.start();return a.exec(); }??運行:
??上面代碼的運行結果:
??槽函數執行時的所在線程和信號發送操作的所在線程并不是同一個,前者位于main線程中,后者位于子線程中。
??由此可以引申兩個問題:
??(1) 二者同屬于子線程類,程序運行時發送信號操作在子線程完成,對應的槽函數卻是在main線程執行,究其原因,得從Qt對象的依附性說起。
??在Qt編程中,默認情況下,對象依附于創建自身的線程,例如上面代碼中TestThread對象t它是在main()函數中創建的,那么t依附于主線程,而槽函數在其所依附的線程中被調用執行,因此,槽函數TestThread_Slot()是在main線程中執行。
??要想讓TestThread_Slot()函數運行在main()創建的子線程中,可以使用moveToThread()函數更改TestThread對象所依附的線程:
int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);qDebug() << "main: tid = " << QThread::currentThreadId();TestThread t;t.moveToThread(&t); //更改TestThread對象的依附性到t線程t.start();return a.exec(); }??運行:
??需要強調,這樣的做法在實際工程中并不采取。因為QThread應該被看做是操作系統線程的接口或控制點,而不應該包含需要在新線程中運行的業務邏輯代碼。顯然,修改TestThread對象所依附的線程恰恰是打算在線程類對象中實現業務邏輯代碼。
??(2) 若要對上面的代碼的運行結果做個轉換:槽函數運行時所依賴的是子線程,信號發送操作是在main()線程中完成,何以實現?
//定義一個存放槽函數的類MyObject,它繼承自QObject //MyObject.h #ifndef MYOBJECT_H #define MYOBJECT_H#include <QObject> #include <QThread> #include <QDebug>class MyObject : public QObject {Q_OBJECT public:explicit MyObject(QObject *parent = 0);public slots: //定義兩個槽函數void getStarted();void MyObjectSlot(); };#endif // MYOBJECT_H//MyObject.cpp #include "MyObject.h"MyObject::MyObject(QObject *parent) :QObject(parent) { }void MyObject::getStarted() {qDebug() << "MyObject::getStarted(): tid = " << QThread::currentThreadId(); }void MyObject::MyObjectSlot() {qDebug() << "MyObject::MyObjectSlot(): tid = " << QThread::currentThreadId(); }在main函數中,定義線程對象和MyObject對象,并將MyObject的依附性改為子線程 int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);qDebug() << "main: tid = " << QThread::currentThreadId();TestThread t;MyObject m;//m.moveToThread(&t);QObject::connect(&t, SIGNAL(TestThread_Signal()), &m, SLOT(MyObjectSlot()));t.start(); //run()函數的實現體和上面的一樣,會發射TestThread_Signal()信號。該信號在這里還增加連接了MyObjectSlot的槽,所以除了會觸發//TestThread_Slot()函數外還會觸發MyObjectSlot()return a.exec(); }??運行:
??TestThread::TestThread_Slot()得到調用,但是MyObject::MyObjectSlot()卻沒有,這就要引入事件循環的概念。當發送信號操作所在的線程和槽函數執行時所在的線程是不同線程時,就需要事件循環的支持。如main函數中的”a.exec()”函數的調用也是開啟事件循環,QThread線程類同樣提供了exec()函數用于開啟線程的事件循環,發送信號和槽函數位于不同的線程時,只有槽函數所在線程開啟了事件循環,它才能在對應信號發射后被調用。無論事件循環是否開啟,信號發送后會直接進入槽函數所依附的線程的事件隊列,然而,只有開啟了事件循環,對應的從函數才會在線程中得到調用。
??從上圖可知,事件循環是一個無止盡循環,事件循環結束之前,exec()函數后的語句無法得到執行。問線程如何正常結束?這就需要quit()或者載exit()函數用于結束事件循環。
??在TestThread::run()函數中增加exec()函數的調用:
void TestThread::run() {qDebug() << "void TestThread::run(): tid = " << currentThreadId();emit TestThread_Signal();exec(); //開啟子線程的事件循環qDebug() << "hello"; }int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);qDebug() << "main: tid = " << QThread::currentThreadId();TestThread t;MyObject m;m.moveToThread(&t);QObject::connect(&t, SIGNAL(TestThread_Signal()), &m, SLOT(MyObjectSlot()));t.start(); //開啟線程執行函數t.wait(8 * 1000); //休眠等待8s,確保事件隊列中的槽函數得到執行后,再讓子線程結束事件循環t.quit();return a.exec(); }??運行:
??需要注意的是,當信號的發送與對象的槽函數的執行在不同線程中時,可能會產生臨界資源的競爭問題。
2. 信號與槽的連接方式
??信號與槽的連接函數的原型為:
bool QObject::connect (const QObject * sender, const char * signal, const QObject * receiver,const char * method,Qt::ConnectionType type = Qt::AutoConnection)- 1
- 2
- 3
- 4
- 5
??其中第5個參數決定信號與槽的連接方式,用于決定槽函數被調用時的相關行為。
Qt::AutoConnection 默認連接 Qt::DirectConnection 槽函數立即調用 Qt::BlockingQueuedConnection 同步調用 Qt::QueuedConnection 異步調用 Qt::UniqueConnection 單一連接??(1) Qt::DirectConnection(立即調用)
??直接在發送信號的線程中調用槽函數(發送信號和槽函數位于同一線程),等價于槽函數的實時調用。
??(2) Qt::QueuedConnection(異步調用)
??信號發送至目標線程的事件隊列(發送信號和槽函數位于不同線程),交由目標線程處理,當前線程繼續向下執行。
??(3) Qt::BlockingQueuedConnection(同步調用)
??信號發送至目標線程的事件隊列,由牧寶想線程處理。當前線程阻塞等待槽函數的返回,之后向下執行。
??(4) Qt::AutoConnection(默認連接)
??當發送信號線程=槽函數線程時,效果等價于Qt::DirectConnection;
??當發送信號線程!=槽函數線程時,效果等價于Qt::QueuedConnection。
??Qt::AutoConnection是connect()函數第5個參數的默認值,也是實際開發中字常用的連接方式。
??(5) Qt::UniqueConnection(單一連接)
??功能和AutoConnection相同,同樣能自動確定連接類型,但是加了限制:同一個信號和同一個槽函數之間只能有一個連接。
Qt線程同步之互斥鎖同步
一、直接使用QMutex進行同步
創建線程方法:繼承自QThread,重寫void run()函數,調用成員start()啟動線程,start()中可加入優先級參數。
互斥鎖同步方法:void run()函數中使用QMutex來實現同步,當多個線程訪問共享變量時,應使用lock/trylock和unlock將對共享變量的操作代碼包裹,以保證同步訪問共享變量。(C++中引起線程安全的共享資源只有兩種:全局變量和靜態變量)
示例代碼中兩個Thread均繼承自QThread(),為了保證互斥鎖對兩個線程均可見,QMutex在一個線程CPP文件中定義,另一個線程文件做extern聲明。
示例代碼如下:
thread.h
?
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QtCore>
#include <QMutex>
class MyThread:public QThread
{
public:
MyThread(QString name);
void run();
private:
QString mName;
};
#endif // MYTHREAD_H
?
thread.cpp
?
#include "mythread.h"
#include <QDebug>
int i=50;
QMutex mutex;
MyThread::MyThread(QString name):QThread(),mName(name)
{
qDebug()<<"creating.."<<endl;
}
void MyThread::run()
{
qDebug()<<this->mName<<"running.."<<endl;
mutex.lock();
/*
for(;i<100;i++)
{
qDebug()<<this->mName<<i<<endl;
}
*/
i++;
i*=2;
qDebug()<<this->mName<<i<<endl;
mutex.unlock();
qDebug()<<this->mName<<"stop running.."<<endl;
sleep(1);
}
thread2.h
?
?
#ifndef MYTHREAD2_H
#define MYTHREAD2_H
#include <QThread>
#include <QMutex>
class MyThread2:public QThread
{
public:
MyThread2(QString name);
void run();
private:
QString mName;
};
#endif // MYTHREAD2_H
?
thread2.cpp
?
#include "mythread2.h"
#include <QDebug>
extern int i;
extern QMutex mutex;
MyThread2::MyThread2(QString name):QThread(),mName(name)
{
qDebug()<<"creating.."<<endl;
}
void MyThread2::run()
{
qDebug()<<this->mName<<"running.."<<endl;
mutex.lock();
/*
for(;i>0;i--)
{
qDebug()<<this->mName<<i<<endl;
}
*/
i--;
i/=2;
qDebug()<<this->mName<<i<<endl;
mutex.unlock();
qDebug()<<this->mName<<"stop runnning.."<<endl;
sleep(1);
}
main.cpp
?
?
#include <QCoreApplication>
#include "mythread.h"
#include "mythread2.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread thread1("thread1");
MyThread2 thread2("thread2");
thread1.start(QThread::HighestPriority);//高優先級的任務獲得更多的CPU使用比,因此先計算完成
thread2.start();//相對低優先級的任務會后完成
thread1.wait();
qDebug()<<"thread1 is done!"<<endl;
thread1.wait();
qDebug()<<"thread2 is done!"<<endl;
// thread1.exit();
return a.exec();
}
實驗結果:
?
?
二、使用互斥鎖類QMutexLocker(淺談RAII)
問題:使用QMutex的上鎖、解鎖操作直接同步會有一個致命缺陷:當代碼提前退出時(如函數中多處return或C++拋出異常),可能并未執行unlock(),若其他線程采用lock()阻塞式上鎖會一直被阻塞等待釋放,導致資源泄露。
解決:根據RAII的思想,我們應該盡量使用對象管理資源,構造時獲取互斥鎖,析構時釋放鎖。(參見Effective C++條款13)
?
具體來講,QMutexLocker作為一個便利類,可以解決以下兩種函數有多個出口的情況:
(1)第一種情況是函數內部多次return,如果直接使用QMutex上鎖,必須保證每個return之前都及時釋放鎖資源(每個return前都要加上unlock()),否則當前線程的run()退出時另一個線程的run()無法獲取鎖,造成死鎖。如下例所示:
?
int complexFunction(int flag)
{
QMutexLocker locker(&mutex);
int retVal = 0;
switch (flag) {
case 0:
case 1:
return moreComplexFunction(flag);
case 2:
{
int status = anotherFunction();
if (status < 0)
return -2;
retVal = status + flag;
}
break;
default:
if (flag > 10)
return -1;
break;
}
return retVal;
}
可以看到如果使用QMutex進行上鎖,在線程的run()函數中調用該函數,一旦該函數在中途return,又沒有及時調用unlock()就會導致互斥鎖永遠沒有機會釋放。除非在每一個return前加上QMutex的unlock()。
?
如果我們按上述代碼所示,使用QMutexLocker管理QMutex,由于函數中的QMutexLocker是一個局部對象,因此return的時候一定會調用析構并在析構內部完成互斥鎖的釋放。
(2)另一種情況是C++拋出異常的情況:C++標準里明確規定拋出異常時仍能保證局部對象的析構調用,這也是RAII技術的保證。也就是說由于QMutexLocker是局部對象,所以一旦遇到函數退出時,局部對象被釋放都會調用析構,析構內部會釋放鎖。(參見Effective C++條款29)
至于為何C++拋出異常時仍能保證釋放局部對象(棧上變量),這是C++標準規定,請參看:
https://segmentfault.com/q/1010000002498987
?
因此,Qt提供了互斥鎖類QMutexLocker,當QMutexLocker作為局部對象時,函數中途return或拋出異常時均會調用析構釋放對象,而該類的析構函數內部調用了參數綁定的QMutex對應的unlock()函數,這也是RAII技術的基礎保證。空口無憑,如圖為證,這是QMutexLocker內部的析構函數實現:
?
可以看到,析構里面調用unlock()函數,而unlock()函數內部調用mutex()->unlock(),mutex()是一個常量函數,返回QMutexLocker綁定的QMutex,因此mutex()->unlock()調用了QMutex的unlock()函數,完成了對互斥鎖的解鎖。如圖:
可以看到上方的QMutex類中將QMutexLocker聲明成了QMutex的友元!因此QMutexLocker可以在析構中調用QMutex的unlock()完成鎖資源的釋放。這就使得當run()函數有多個出口退出時(多處return或拋出異常),析構被調用并及時完成互斥鎖的釋放,從而避免鎖資源的泄露問題。
另外一個用到RAII思想的技術比如C++STL的智能指針,也是為了避免堆上空間未及時釋放的情況。如果使用普通指針申請堆空間,函數中途拋出異常(比如另一個指針申請空間失敗,拋出bad_alloc異常),那該指針申請的空間將無法釋放,有人說使用捕獲異常在catch中釋放所有資源,比如此處泄露的內存,但這并不是個好辦法,于是根據RAII思想,智能指針產生了,當智能指針的引用計數減為0時會釋放這塊內存(delete)。
看到這里,終于放心了嗎?這是為什么Qt也推薦使用QMutexLocker的原因:RAII技術可以讓我們寫出異常安全的代碼。
Qt中多線程的使用
最近項目中提出了新的需求,需要采集和收集圖片。因為是高速相機,按照幀率至少200FPS的速度計算的話,30秒時間的圖片也就是6000幀,同時要保持盡量不掉幀。因此這個開發任務就有兩個重點:
?
- 如何能夠在收集圖片的時候不掉幀,把30秒鐘采集到的圖片盡可能的采集和收集到;
- 在保存文件時,如何能夠保證界面的及時響應,因為需要存放大量的圖片,在只有一個線程工作的情況下,勢必不能保持界面的良好響應;
?
對于第一點,個人采取的方案是只能是在采取完所有的圖片之后,再將圖片從內存中寫入到硬盤中。這樣保證了采集速率和緩速率之間的瓶頸。但是不足的是,如果按照圖片的分辨率是640*480,那么6000張圖片就是1.7G。所以,會占據很大的內存。同時需要考慮到合適的數據結構,對于1.7個G的數據量,如果采用數組是不合適的,因為數組是連續的分配內存空間,那么對于內存較小的內存空間,就會分配失敗。那么比較好的數據結構,應該是采用鏈表。因此我沒有采用vector的數據結構,而是采用了list的數據結構。
對于第二點,當內存中已經采取到6000幀數據,從內存寫入到硬盤中去的時候,會耗費比較大的時間以及CPU的運行,因此如果在單線程中,勢必會影響到界面的良好響應,所以必須采用多線程來實現。目前為止,我的技能只有兩個,一個是pthread線程機制,一個是不太熟悉的QThread機制,也就是Qt提供的多線程機制,因為不了解,所以我采用了pthread線程機制,但是在實現中出現了個一個重要的問題:
?
- 實際上,得到需要的數據后,可以很好的保持界面的響應,同時保持“不可見的后臺”寫入數據的線程。這一點是實現了的。但是我還希望通過在寫入的過程中,實時更新寫入狀態,通過進度條來顯示寫入進度,這樣就出現了一個錯誤,即Qt不支持在多線程(這里的多線程指的是pthread開啟的線程)之間傳遞信號。至于這個問題,我沒有找到很好的解決方案,于是乎我希望通過一個全局變量,在兩個線程之間共享全局變量的狀態,這樣即達到了更新寫入狀態的目的;
- 對于1的解決方案,還有一個就是通過QThead線程來實現。因為QThead是繼承QObject對象的,而QObject是支持Qt信號槽的接觸。因此我猜想有可能會可能有用,因此去學習了如果使用Qt線程,下面介紹一下我在實際中的使用。
?
- Qt線程學習
run()是線程的入口,就像main()對于應用程序的作用。QThread中對run()的默認實現調用了exec(),從而創建一個QEventLoop對象,由其處理該線程事件隊列(每一個線程都有一個屬于自己的事件隊列)中的事件,用代碼簡單描述過程如下。
由此可見,exec()在其內部不斷做著循環遍歷事件隊列的工作,調用QThread的quit()或exit()方法使停止工作,盡量不要使用terminate(),該方法過于粗暴,造成資源不能釋放,甚至互斥鎖還處于加鎖狀態。
- 關于QThread使用的簡單討論
一般而言,我們會去繼承QThread,然后將線程運行需要的參數傳遞進去,然后調用run方法,內部調用成員變量運行。比較常見的案例如下:
那么這個簡單的案例最大的難點以及疑點就是moveThread的使用。為何需要這么做呢?以下內容摘自網頁http://blog.jobbole.com/84149/,我任務講得蠻好的,因此分享給大家:
moveToThread()函數通知Qt準備好事件處理程序,讓擴展的信號(signal)和槽(slot)在指定線程的作用域中調用。QThread是線程的接口,所以我們是在告訴這個線程在“它內部”執行代碼。我們也應該在線程運行之前做這些事。即使這份代碼看起來可以運行,但它很混亂,并不是QThread設計中的用法(QThread中寫的所有函數都應該在創建它的線程中調用,而不是QThread開啟的線程)。
QThread是被設計來作為一個操作系統線程的接口和控制點,而不是用來寫入你想在線程里執行的代碼的地方。我們(面向對象程序員)編寫子類,是因為我們想擴充或者特化基類中的功能。我唯一想到的繼承QThread類的合理原因,是添加QThread中不包含的功能,比如,也許可以提供一個內存指針來作為線程的堆棧,或者可以添加實時的接口和支持。用于下載文件、查詢數據庫,或者做任何其他操作的代碼都不應該被加入到QThread的子類中;它應該被封裝在它自己的對象中。
通常,你可以簡單地把類從繼承QThread改為繼承QObject,并且,也許得修改下類名。QThread類提供了start()信號,你可以將它連接到你需要的地方來進行初始化操作。為了讓你的代碼實際運行在新線程的作用域中,你需要實例化一個QThread對象,并且使用moveToThread()函數將你的對象分配給它。你同過moveToThread()來告訴Qt將你的代碼運行在特定線程的作用域中,讓線程接口和代碼對象分離。如果需要的話,現在你可以將一個類的多個對象分配到一個線程中,或者將多個類的多個對象分配到一個線程。換句話說,將一個實例與一個線程綁定并不是必須的。
因此合理使用moveThread是QThread需要注意的地方。
下面貼出我自己在使用QThread的案例,不知大家還記得我最開始提出我在使用多線程的難點,即共享線程間的狀態,即線程間發送信號。因此在這里,我應該是希望我創建的線程依賴于我創建它的線程,這樣我創建線程發送的信號就可以被我的主線程中捕捉使用。因此moveThread是不必須的。
具體實現:
?
#include "savethread.h"
SaveThread::SaveThread(QObject *parent)
: QThread(parent)
{
IsSaving=false;
}
SaveThread::~SaveThread()
{
}
void SaveThread::SetParameter(std::list<cv::Mat>buffer,QString format)
{
buffer_temp=buffer;
this->format=format;
}
bool SaveThread::GetStatus()
{
return IsSaving;
}
void SaveThread::run()
{
int i=1;
IsSaving=true;
std::string picName;
while(!buffer_temp.empty())
{
cv::Mat temp=buffer_temp.back();
Mat finalMat=seprateImage(temp);
picName="./圖像采集/";
picName+=QString::number(i).toStdString();
picName+=format.toStdString();
cv::imwrite(picName,finalMat);
buffer_temp.pop_back();
emit sig_progress_value(i);
i=i+1;
}
IsSaving=false;
}
cv::Mat SaveThread::seprateImage(cv::Mat srcImage)
{
int height=480;
int width=648;
Mat temp_right=cv::Mat::zeros(height,width/2,CV_8UC1);
Mat finalMat=Mat::zeros(height,width,CV_8UC1);
if(width%2==0)
{
int j=0;
int k=width/2-1;
for(int i=0;i<srcImage.cols;i++)
{
if(i%2==0)
{
srcImage.col(i).copyTo(finalMat.col(j));j++;
}
else
{
srcImage.col(i).copyTo(temp_right.col(k));k--;
}
}
for(int i=width/2;i<width;i++)
{
temp_right.col(i-width/2).copyTo(finalMat.col(i));
}
}
return finalMat;
}
在主線程中調用:
?
?
saveThread=new SaveThread(this);
saveThread->SetParameter(buffer,format);
connect(saveThread,SIGNAL(sig_progress_value(int)),this,SLOT(onUpdateProgress(int)));
saveThread->start();
?
以上是大致的實現邏輯,具體實現的細節有點多,篇幅有限,可以給大家看看最后的效果:因為傳遞圖片大小的限定,不能給出較長的時間長度的顯示,所以給了大致的顯示:
?
?
?
總結
- 上一篇: VScode 格式化代码快捷键、修改快捷
- 下一篇: Qt中在控件上绘图