Qt基礎開發之Qt多執行緒類QThread與Qt定時器類QTimer的詳細方法與例項
Qt多執行緒
我們之前的程式都是單執行緒執行,接下來我們開始引入多執行緒。就相當於以前的一個人在工作,現在多個人一起工作。
Qt中非常有必要使用多執行緒,這是因為,Qt應用是事件驅動型的,一旦某個事件處理函式處理時間過久,就會造成其它的事件得不到及時處理。
Qt中使用QThread來管理執行緒,一個QThread物件,就是一個執行緒。QThread物件也有訊息循序exec()函式,用來處理自己這個執行緒的事件。
Qt實現多執行緒有兩種方式
1、Qt第一種建立執行緒方式
首先要繼承QThread
重寫虛擬函式QThread::run
[virtual protected] void QThread::run() /* * 基類QThread的run函式只是簡單啟動exec()訊息迴圈 */
例如:
#include <QApplication> #include <QThread> #include <QDebug> class MyThread : public QThread { public: void run() { qDebug() << "QThread begin" << endl; qDebug() << "child thread" << QThread::currentThreadId() << endl; QThread::sleep(5); qDebug() << "QThread end" << endl; exec(); } }; int main(int argc,char** argv) { QApplication app(argc,argv); MyThread thread; thread.start(); qDebug() << "main thread" << QThread::currentThreadId() << endl; QThread::sleep(5); qDebug() << "main thread" << QThread::currentThreadId() << endl; thread.quit(); qDebug() << "main thread thread.quit()" << endl; tread.wait(); qDebug() << "main thread thread.wait()" << endl; return app.exec(); }
使用QThread的quit可以退出執行緒的訊息迴圈,有時候不是馬上退出,需要等到cpu的控制權交還給執行緒的exec()。
一般在子執行緒退出的時候需要主執行緒去回收資源,可以呼叫QThread的wait,等待子執行緒的退出,然後回收資源.
2、Qt第二種建立執行緒方式
繼承 QObject
例項化一個QThread物件
實現槽函式.
QObject子類物件通過moveToThread將自己放到執行緒QThread物件中.
呼叫QThread物件的start函式啟動執行緒
必須通過發射訊號來讓槽函式線上程中執行,發射的訊號存放線上程exec()訊息佇列中。
例如:
mywork.h
#ifndef MYWORK_H #define MYWORK_H #include <QThread> #include <QDebug> class MyWork : public QObject { Q_OBJECT public slots: void workSlot() { qDebug() << "QThread begin" << endl; qDebug() << "child thread" << QThread::currentThreadId() << endl; QThread::sleep(5); qDebug() << "QThread end" << endl; } }; #endif // MYWORK_H
widget.cpp
#include <QApplication> #include <QThread> #include <QDebug> #include "mywork.h" int main(int argc,char** argv) { qDebug() << "main thread" << QThread::currentThreadId() << endl; QApplication app(argc,argv); QThread thread; MyWork work; work.moveToThread(&thread); QObject::connect(&thread,SIGNAL(started()),&work,SLOT(workSlot())); thread.start(); QThread::sleep(6); qDebug() << "thread is runing" << thread.isRunning() << endl; thread.quit(); //呼叫quit讓執行緒退出訊息迴圈,否則執行緒一直在exec迴圈中 thread.wait(); //呼叫完quit後緊接著要呼叫wait來回收執行緒資源 qDebug() << "thread is runing" << thread.isRunning() << endl; return app.exec(); }
需要特別注意的是:
- 線槽函式已經執行完進入執行緒exec()中,可以通過發射訊號重新讓槽函式線上程中執行。也可以通過quit()退出執行緒exec()。
- QObject派生類物件,將要呼叫moveToThread,不能指定一個主執行緒父物件託管記憶體。
- QWidget的物件及派生類物件都只能在GUI主執行緒執行,不能使用moveToThread移到子執行緒中,即使沒有指定父物件。
多執行緒物件記憶體釋放
既然QObject物件無法託管記憶體物件,那麼到底是先釋放執行緒物件,還是先釋放這個QObject物件?
先把QObject線上程迴圈中釋放(使用QObject::deleteLater函式),然後QThread::quit,然後QThread::wait。
例如:
mywork.h
#ifndef MYWORK_H #define MYWORK_H #include <QThread> #include <QDebug> class MyWork : public QObject { Q_OBJECT public: ~MyWork() { qDebug() << __FUNCTION__ << endl; } public slots: void workSlot() { while(1) { qDebug() << "Work begin" << endl; QThread::sleep(5); qDebug() << "work end" << endl; } } void otherWorkSlot() { qDebug() << "otherWork begin" << endl; QThread::sleep(5); qDebug() << "otherWork end" << endl; } }; #endif // MYWORK_H
widget.h
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include "mywork.h" class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = 0); ~Widget(); private: QThread *_thread; MyWork * _myWork; }; #endif // WIDGET_H
widget.cpp
#include "widget.h" #include <QPushButton> #include "mywork.h" #include <QThread> #include <QHBoxLayout> Widget::Widget(QWidget *parent) : QWidget(parent) { _myWork = new MyWork; _thread = new QThread(this); _myWork->moveToThread(_thread); _thread->start(); QPushButton *pb0 = new QPushButton("work",this); QPushButton *pb1 = new QPushButton("pb",this); QHBoxLayout *hBox = new QHBoxLayout(this); hBox->addWidget(pb0); hBox->addWidget(pb1); this->setLayout(hBox); /*發射訊號給在另外一個執行緒的物件的佇列中*/ connect(pb0,SIGNAL(clicked()),_myWork,SLOT(workSlot())); connect(pb1,SLOT(otherWorkSlot())); /*推薦用法釋放記憶體*/ //connect(_thread,SIGNAL(finished()),SLOT(deleteLater())); } Widget::~Widget() { _myWork->deleteLater(); //一定要在QThread執行緒退出之前 _thread->quit(); _thread->wait(); }
3、Qt執行緒的同步
多執行緒在訪問同時一個資源,(例如:多個執行緒可操作的變數、函式等),到底誰來使用這個資源是一個問題,就像一大群人去搶同一塊蛋糕,可能其中一個人搶到,更有可能蛋糕被搶個稀爛。在多執行緒中,這個叫作競爭冒險。那麼我們需要定一個規則來約束每一個人,比如:每個人排隊來領蛋糕,這個在多執行緒中叫作同步方法。
需要注意的是,同步不是同時,而是有序進行。
3.1、互斥鎖
Qt中的互斥鎖是QMutex,不繼承任何Qt基類,使用QMutex來鎖共享資源,哪個執行緒搶到鑰匙,哪個執行緒就有這個資源的使用權,其它執行緒等待這個執行緒使用完資源並歸還鑰匙,然後它們再去搶鑰匙。
例如:
QMutex mutex; //這物件一般定義在多個執行緒能訪問的地方 mutex.lock(); //多個執行緒呼叫這個函式去獲取鎖,沒有獲取到的執行緒,將阻塞等待在這個函式上。 mutex.unlock(); //釋放鎖
QMutex::lock函式會讓執行緒等待獲取鎖,如果不想等待,可以使用一下函式替換:
bool QMutex::tryLock(int timeout = 0) /* *引數 int timeout:等到timeout毫秒,不管有沒獲取到鎖都返回,timeout為0時,直接返回。 *返回值 true代表獲取到鎖,false沒有獲取到 */
有時候我們會忘記釋放鎖,Qt還為我們提供了管理鎖的類QMutexLocker
QMutex mutex; void func() { QMutexLocker locker(_mutex); //QMutexLocker最好例項化成棧物件,釋放之前將QMutex解鎖。 }
以上例子,一旦func()執行完成locker自動釋放,釋放之前先解鎖。
3.2、訊號量
互斥鎖保護的資源同一時刻只能有一個執行緒能夠獲取使用權,有些資源是可以限定多個執行緒同時訪問,那麼這個時候可以使用訊號量。在Qt中訊號量為QSemaphore。
QSemaphore sem(2) //初始化訊號量為2 sem.acquire(); //訊號量部位0的時候,呼叫這個函式會讓訊號量-1,一旦訊號量為零,阻塞等待 semaphore.release(); //使訊號量+1
4、Qt定時器QTimer
定時器可以隔一段時間發出訊號,通過接收這個訊號來處理一些定時任務,需要注意的是,定時器並沒有開啟一個新執行緒。Qt中的定時器是QTimer繼承自QObject。
通過QTimer::start()來啟動定時器
void start(int msec) /* *定時msec毫秒後,發射timeout()訊號 */
通過連結訊號timeout()來處理一些定時任務,例如:
#include "dialog.h" #include <QTimer> #include <qdebug.h> Dialog::Dialog(QWidget *parent) : QDialog(parent) { QTimer *timer = new QTimer(this); connect(timer,SIGNAL(timeout()),this,SLOT(doSomeThing())); timer->start(3000); } void Dialog::doSomeThing() { qDebug() << __FUNCTION__ << endl; } Dialog::~Dialog() { }
本文主要介紹了Qt多執行緒類QThread與Qt定時器類QTimer的詳細方法與例項,更多關於Qt開發知識請檢視下面的相關連結