QT5 Thread線程

QT5 Thread線程繼承QThread方式node

一.首先分析一下 QTimer Class與 Sleep()函數之間的祕密git

QTimer *t = new QTimer(*parent); //建立QTimer 對象程序員

t->start(_time); //計時開始每隔_time時間自動觸發&QTimer::timeout信號github

t->stop(); //結束計時數據庫

Sleep() //windows.h裏面的系統延時函數windows

 

經過以上方法實現案例:網絡

//button 槽函數
void Widget::on_buttonstart_clicked()
{
    t->start(2000);
    Sleep(3000);
  qDebug() << "hello world!"; }
//timeout信號處理函數
connect(t, &QTimer::timeout, [=]() { ui->lcd_1->display(++i); });

分析,在沒有Sleep()函數的狀況下:數據結構

點擊開始立馬在控制檯顯示hello world!;每隔2秒lcd顯示+1;多線程

有Sleep()的存在後;點擊開始程序本質是想每隔2秒lcd顯示+1;3秒後控制檯顯示hello world!;函數

最終結果是:

點擊開始,計時器計時,2秒後,不運行connect();3秒後connect()第一次運行;再過4秒,第二次timeout信號觸發,再次運行connect();

最終顯示結果爲; 過期3秒制臺顯示hello world!lcd顯示 1 再過期1秒顯示2 再過2秒顯示3 依次通過2秒顯示累加1;

 

二.線程的引入;

 若是咱們想要的結果是,點擊按鈕,lcd每一秒顯示+1, 3秒控制檯回顯hello world!  也就是Sleep(3000)顯示hello world!並不會去影響到Qtrimer計時;

 

 

 單首創建線程A,在A線程是實現延時3秒輸出hello world!;

1.一個簡單的控制檯線程例子

新建一個qt控制檯程序 自定義一個類  這裏就叫class mythread

//mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H #include <QThread> class myThread: public QThread { public: myThread(); void run(); //聲明繼承於QThread虛函數 run() }; #endif // MYTHREAD_H
//mythread.cpp

#include "mythread.h"
#include <QDebug>

myThread::myThread()
{

}
void myThread::run()
{
  qDebug() <<  "hello world!"; //複寫QThread類的 run()函數
}
//main.cpp
#include <QCoreApplication>
#include "mythread.h"   //包涵頭文件
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    myThread *thread1 = new myThread; //新建線程對象
    thread1->start();  //啓動線程
 
    return a.exec();
}

上例啓動了一個新線程中輸出hello world!

改進上例:

//mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H #include <QThread> class myThread: public QThread { public: myThread(); void run(); QString name; //添加一個 name 對象 }; #endif // MYTHREAD_H
//mythread.cpp
#include "mythread.h"
#include <QDebug>

myThread::myThread()
{

}
void myThread::run()
{
  qDebug() <<  this->name << "hello world!";
    //添加一個for循環
  for(int i = 0; i < 1000; i++)
  {
      qDebug() << this->name << i;
  }
}
//main.cpp

#include <QCoreApplication>
#include "mythread.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    //連續建立三個子線程
    myThread *thread1 = new myThread;
    thread1->name = "mythred1";
    thread1->start();
    
    myThread *thread2 = new myThread;
    thread2->name = "mythred2";
    thread2->start();

    myThread *thread3 = new myThread;
    thread3->name = "mythred3";
    thread3->start();

    return a.exec();
}

運行結果:

結果顯示輸出爲無序輸出,結論三個線程徹底獨立運行,互不影響;

2.三個線程,天然會有優先權的問題,也就是cpu,先運行哪一個線程;下面讓咱們來談談優先權

線程權限由線程啓動函數start(Priority枚舉)控制

如上例:在啓動函數中加入枚枚量,具體參數可查幫助文檔:

3.QMutex 類

QMutex類提供了線程之間的訪問序列化。
QMutex的目的是保護對象,數據結構或代碼段,以便一次只有一個線程能夠訪問它(這與Java synchronized關鍵字相似)。 QMutexLocker一般最好使用互斥鎖,由於這樣能夠很容易地確保鎖定和解鎖一致地執行。

  int number = 6;

  void method1()
  {
      number *= 5;
      number /= 4;
  }

  void method2()
  {
      number *= 3;
      number /= 2;
  }

 若是線程thread1 ,thread2分別順序執行method1(),method2();最終結果將會是:

 // method1()
  number *= 5;        // number is now 30
  number /= 4;        // number is now 7

  // method2()
  number *= 3;        // number is now 21
  number /= 2;        // number is now 10

number = 10;

但若是線程1在行動時,被系統掛載,或其它種種因素受到延時運行,好比有更高優先級線程申請運行,而線程2確並不受影響,最終結果將會是:

  // Thread 1 calls method1()
  number *= 5;        // number is now 30

  // Thread 2 calls method2().
  //
  // Most likely Thread 1 has been put to sleep by the operating
  // system to allow Thread 2 to run.
  number *= 3;        // number is now 90
  number /= 2;        // number is now 45

  // Thread 1 finishes executing.
  number /= 4;        // number is now 11, instead of 10

此時number = 11; 並不等於10; 同一程序運行不一樣結果,這是不容許的

此時就要藉助於QMutex 類;

 QMutex mutex;
  int number = 6;

  void method1()
  {
      mutex.lock();
      number *= 5;
      number /= 4;
      mutex.unlock();
  }

  void method2()
  {
      mutex.lock();
      number *= 3;
      number /= 2;
      mutex.unlock();
  }

當你在一個線程中調用lock()時,其餘線程會試圖在同一個地方調用lock(),直到得到鎖的線程調用unlock()。 lock()的一個非阻塞替代是tryLock()。
QMutex在非競爭狀況下進行了優化。 若是該互斥體沒有爭用,則非遞歸QMutex將不分配內存。 它的構建和銷燬幾乎沒有開銷,這意味着有不少互斥體做爲其餘類的一部分是很好的。

當線程1被cpu延時處理,而線程2處理到method2()時自動會進入method1()繼續處理number /=4;再回到method2();而此時若是線程1繼續執行時,自動又會進入到method2();

4.QThread 啓動暫停等待信號與槽控制實例

 延續控制檯線程例子 在每一個線程後面加上 thread1->wait(); qDebug() << "hello world!";

預期的結果將會是, 在線程輸出完後纔會輸出hello world!

#include <QCoreApplication>
#include "mythread.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    //連續建立三個子線程
    myThread *thread1 = new myThread;
    thread1->name = "mythred1";
    thread1->start();
    
    thread1->wait();
    qDebug() << "hello world!";

    return exec();
}

 如今轉到GUI下,下面一個例子:

//自定義線程類,頭文件
#ifndef NITHREAD_H
#define NITHREAD_H

#include <QThread>

class nithread : public QThread
{
    Q_OBJECT
public:
    explicit nithread(QObject *parent = 0);
    bool stop;

signals:
    void sig(int);

protected:
    void run();

public slots:
};

#endif // NITHREAD_H 
//自定義線程類cpp
#include "nithread.h"
#include <QMutex>
nithread::nithread(QObject *parent) : QThread(parent)
{

}

void nithread::run()
{
    for(int i = 0; i < 100; i++)
    {
        QMutex mutex;
        mutex.lock();
        if(this->stop) break;
        mutex.unlock();
        emit sig(i);
        msleep(100);
    }
}
//GUi窗口類頭文件
#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>
#include <nithread.h>

namespace Ui {
class Dialog;
}

class Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Dialog(QWidget *parent = 0);
    ~Dialog();

private slots:
    void on_buttonstart_clicked();
    void lot(int);

    void on_buttonstop_clicked();

private:
    Ui::Dialog *ui;
    nithread *threadd;
};

#endif // DIALOG_H
//GUI類cpp
#include "dialog.h"
#include "ui_dialog.h"

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);
    threadd = new nithread(this);
    connect(threadd, SIGNAL(sig(int)), this, SLOT(lot(int)));
}

Dialog::~Dialog()
{
    delete ui;
}

void Dialog::on_buttonstart_clicked()
{
    threadd->start();
}

void Dialog::lot(int num)
{
    ui->numberlabel->setText(QString::number(num));
}

void Dialog::on_buttonstop_clicked()
{
    threadd->stop = true;
}
//main.cpp
#include "dialog.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Dialog w;
    w.show();

    return a.exec();
}

最終結果:

當點擊start 開啓線程 stop 中止線程 經過顯號與槽顯示結果

然而方法一Thread線程繼承QThread方式,在實際問題中卻有着不少的問題以下文簡介:早在2006年已經被qt工程師提出;(直指此方法是錯誤的用法)

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

咱們(Qt用戶)正普遍地使用IRC來進行交流。我在Freenode網站掛出了#qt標籤,用於幫助你們解答問題。我常常看到的一個問題(這讓我不厭其煩),是關於理解Qt的線程機制以及如何讓他們寫的相關代碼正確工做。人們貼出他們的代碼,或者用代碼寫的範例,而我則老是以這樣的感觸了結:
大家都用錯了!

我以爲有件重要的事情得澄清一下,也許有點唐突了,然而,我不得不指出,下面的這個(假想中的)類是對面向對象原則的錯誤應用,一樣也是對Qt的錯誤應用。

我對這份代碼最大的質疑在於 moveToThread(this);  我見過太多人這麼使用,而且徹底不明白它作了些什麼。那麼你會問,它究竟作了什麼?moveToThread()函數通知Qt準備好事件處理程序,讓擴展的信號(signal)和槽(slot)在指定線程的做用域中調用。QThread是線程的接口,因此咱們是在告訴這個線程在「它內部」執行代碼。咱們也應該在線程運行以前作這些事。即便這份代碼看起來能夠運行,但它很混亂,並非QThread設計中的用法(QThread中寫的全部函數都應該在建立它的線程中調用,而不是QThread開啓的線程)。

在個人印象中,moveToThread(this);  是由於人們在某些文章中看到而且使用而流傳開來的。一次快速的網絡搜索就能找到此類文章,全部這些文章中都有相似以下情形的段落:

  1. 繼承QThread類
  2. 添加用來進行工做的信號和槽
  3. 測試代碼,發現槽函數並無在「正確的線程」中執行
  4. 谷歌一下,發現了moveToThread(this);  而後寫上「看起來的確管用,因此我加上了這行代碼」

我認爲,這些都源於第一步。QThread是被設計來做爲一個操做系統線程的接口和控制點,而不是用來寫入你想在線程裏執行的代碼的地方。咱們(面向對象程序員)編寫子類,是由於咱們想擴充或者特化基類中的功能。我惟一想到的繼承QThread類的合理緣由,是添加QThread中不包含的功能,好比,也許能夠提供一個內存指針來做爲線程的堆棧,或者能夠添加實時的接口和支持。用於下載文件、查詢數據庫,或者作任何其餘操做的代碼都不該該被加入到QThread的子類中;它應該被封裝在它本身的對象中。

一般,你能夠簡單地把類從繼承QThread改成繼承QObject,而且,也許得修改下類名。QThread類提供了start()信號,你能夠將它鏈接到你須要的地方來進行初始化操做。爲了讓你的代碼實際運行在新線程的做用域中,你須要實例化一個QThread對象,而且使用moveToThread()函數將你的對象分配給它。你同過moveToThread()來告訴Qt將你的代碼運行在特定線程的做用域中,讓線程接口和代碼對象分離。若是須要的話,如今你能夠將一個類的多個對象分配到一個線程中,或者將多個類的多個對象分配到一個線程。換句話說,將一個實例與一個線程綁定並非必須的。


我已經聽到了許多關於編寫Qt多線程代碼時過於複雜的抱怨。原始的QThread類是抽象類,因此必須進行繼承。但到了Qt4.4再也不如此,由於QThread::run()有了一個默認的實現。在以前,惟一使用QThread的方式就是繼承。有了線程關聯性的支持,和信號槽鏈接機制的擴展,咱們有了一種更爲便利地使用線程的方式。咱們喜歡便利,咱們想使用它。不幸的是,我太晚地意識到以前迫令人們繼承QThread的作法讓新的方式更難普及。

我也聽到了一些抱怨,是關於沒有同步更新範例程序和文檔來向人們展現如何用最不使人頭疼的方式便利地進行開發的。現在,我能引用的最佳的資源是我數年前寫的一篇博客。()


免責聲明:你所看到的上面的一切,固然都只是我的觀點。我在這些類上面花費了不少精力,所以關於要如何使用和不要如何使用它們,我有着至關清晰的想法。


譯者注:

最新的Qt幫助文檔同時提供了創建QThread實例和繼承QThread的兩種多線程實現方式。根據文檔描述和範例代碼來看,若想在子線程中使用信號槽機制,應使用分別創建QThread和對象實例的方式;若只是單純想用子線程運行阻塞式函數,則可繼承QThread並重寫QThread::run()函數。

因爲繼承QThread後,必須在QThread::run()函數中顯示調用QThread::exec()來提供對消息循環機制的支持,而QThread::exec()自己會阻塞調用方線程,所以對於須要在子線程中使用信號槽機制的狀況,並不推薦使用繼承QThread的形式,不然程序編寫會較爲複雜。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

從Qt4.4開始,能夠採用新的方法也是被稱爲正確的方法也是qt想推廣的方法:

// Worker 類定義 cpp
#include <QtCore> class Worker : public QObject { Q_OBJECT private slots: void onTimeout() { qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId(); } };
//main函數cpp

    int main(int argc, char *argv[])  
    {  
        QCoreApplication a(argc, argv);  
        qDebug()<<"From main thread: "<<QThread::currentThreadId();  
       
        QThread t;  
        QTimer timer;  
        Worker worker;  
       
        QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));  
        timer.start(1000);  
       
        worker.moveToThread(&t);  
       
        t.start();  
       
        return a.exec();  
    }  

總結:

繼承QThread老式方法

1.定義繼承QThread的類A 複寫run()函數;

2.在主線程中實例化A對象a

3.經過調用a->start()啓動線程,線程會自動調用run()虛函數;run不可直接調用;

新方法:

1.建立繼承Obeject的類A 將要在線程中實現的方法在A類中實現

2.在主線程中實例化A對象a,再實例化QThread類對象b

3.經過a.moveToThread(&b);將a對象的實現移入線程b對象做用範圍內運行

4.b->start()啓動線程;

5.經過信號與槽的方式啓動調用A類成員函數;

經常使用函數:

QThread類

start(),//啓動線程;

wait()//等待線程運行結束;

quit(),//線程運行結束退出線程

 

線程與進程區別:

進程是系統爲每一個程序分配有獨立運行空間的運行實例

線程是與進程共用內存空間的一個獨立運行實例;相對而言線程比進程的消耗更低;

結語:

  新版qt5的主要目的也就是讓每一個線程能獨立運行在其線程做用域中,線程與線程以前的交互則經過connect機制;所以對於須要在子線程中使用信號槽機制的狀況,並不推薦使用繼承QThread的形式;些方式僅實用於在只須要在run()中運行一些簡單的函數;

相關文章
相關標籤/搜索