1. 程式人生 > 程式設計 >Qt基礎開發之Qt多執行緒類QThread與Qt定時器類QTimer的詳細方法與例項

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();
}

需要特別注意的是:

  1. 線槽函式已經執行完進入執行緒exec()中,可以通過發射訊號重新讓槽函式線上程中執行。也可以通過quit()退出執行緒exec()。
  2. QObject派生類物件,將要呼叫moveToThread,不能指定一個主執行緒父物件託管記憶體。
  3. 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開發知識請檢視下面的相關連結