QT5?Thread線程的具體實(shí)現(xiàn)
QT5 Thread線程繼承QThread方式
一.首先分析一下 QTimer Class與 Sleep()函數(shù)之間的秘密
QTimer *t = new QTimer(*parent); //創(chuàng)建QTimer 對象 t->start(_time); //計(jì)時(shí)開始每隔_time時(shí)間自動(dòng)觸發(fā)&QTimer::timeout信號(hào) t->stop(); //結(jié)束計(jì)時(shí) Sleep() //windows.h里面的系統(tǒng)延時(shí)函數(shù)
通過以上方法實(shí)現(xiàn)案例:
//button 槽函數(shù) void Widget::on_buttonstart_clicked() { t->start(2000); Sleep(3000); qDebug() << "hello world!"; }
//timeout信號(hào)處理函數(shù)connect(t, &QTimer::timeout, [=]() { ui->lcd_1->display(++i); });
分析,在沒有Sleep()函數(shù)的情況下:
點(diǎn)擊開始立馬在控制臺(tái)顯示hello world!;每隔2秒lcd顯示+1;
有Sleep()的存在后;點(diǎn)擊開始程序本質(zhì)是想每隔2秒lcd顯示+1;3秒后控制臺(tái)顯示hello world!;
最終結(jié)果是:
點(diǎn)擊開始,計(jì)時(shí)器計(jì)時(shí),2秒后,不運(yùn)行connect();3秒后connect()第一次運(yùn)行;再過4秒,第二次timeout信號(hào)觸發(fā),再次運(yùn)行connect();
最終顯示結(jié)果為; 過時(shí)3秒制臺(tái)顯示hello world!lcd顯示 1 再過時(shí)1秒顯示2 再過2秒顯示3 依次經(jīng)過2秒顯示累加1;
二.線程的引入;
如果我們想要的結(jié)果是,點(diǎn)擊按鈕,lcd每一秒顯示+1, 3秒控制臺(tái)回顯hello world! 也就是Sleep(3000)顯示hello world!并不會(huì)去影響到Qtrimer計(jì)時(shí);
單獨(dú)創(chuàng)建線程A,在A線程是實(shí)現(xiàn)延時(shí)3秒輸出hello world!;
1.一個(gè)簡單的控制臺(tái)線程例子
新建一個(gè)qt控制臺(tái)程序 自定義一個(gè)類 這里就叫class mythread
//mythread.h#ifndef MYTHREAD_H #define MYTHREAD_H #include <QThread> class myThread: public QThread { public: myThread(); void run(); //聲明繼承于QThread虛函數(shù) run() }; #endif // MYTHREAD_H
//mythread.cpp #include "mythread.h" #include <QDebug> myThread::myThread() { } void myThread::run() { qDebug() << "hello world!"; //復(fù)寫QThread類的 run()函數(shù) }
//main.cpp #include <QCoreApplication> #include "mythread.h" //包涵頭文件 int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); myThread *thread1 = new myThread; //新建線程對象 thread1->start(); //啟動(dòng)線程 return a.exec(); }
上例啟動(dòng)了一個(gè)新線程中輸出hello world!
改進(jìn)上例:
//mythread.h#ifndef MYTHREAD_H #define MYTHREAD_H #include <QThread> class myThread: public QThread { public: myThread(); void run(); QString name; //添加一個(gè) name 對象 }; #endif // MYTHREAD_H
//mythread.cpp #include "mythread.h" #include <QDebug> myThread::myThread() { } void myThread::run() { qDebug() << this->name << "hello world!"; //添加一個(gè)for循環(huán) 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); //連續(xù)創(chuàng)建三個(gè)子線程 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(); }
運(yùn)行結(jié)果:
結(jié)果顯示輸出為無序輸出,結(jié)論三個(gè)線程完全獨(dú)立運(yùn)行,互不影響;
2.三個(gè)線程,自然會(huì)有優(yōu)先權(quán)的問題,也就是cpu,先運(yùn)行哪個(gè)線程;下面讓我們來談?wù)剝?yōu)先權(quán)
線程權(quán)限由線程啟動(dòng)函數(shù)start(Priority枚舉)控制
如上例:在啟動(dòng)函數(shù)中加入枚枚量,具體參數(shù)可查幫助文檔:
3.QMutex 類
QMutex類提供了線程之間的訪問序列化。
QMutex的目的是保護(hù)對象,數(shù)據(jù)結(jié)構(gòu)或代碼段,以便一次只有一個(gè)線程可以訪問它(這與Java synchronized關(guān)鍵字類似)。 QMutexLocker通常最好使用互斥鎖,因?yàn)檫@樣可以很容易地確保鎖定和解鎖一致地執(zhí)行。
int number = 6; void method1() { number *= 5; number /= 4; } void method2() { number *= 3; number /= 2; }
如果線程thread1 ,thread2分別順序執(zhí)行method1(),method2();最終結(jié)果將會(huì)是:
// 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在行動(dòng)時(shí),被系統(tǒng)掛載,或其它種種因素受到延時(shí)運(yùn)行,比如有更高優(yōu)先級(jí)線程申請運(yùn)行,而線程2確并不受影響,最終結(jié)果將會(huì)是:
// 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
此時(shí)number = 11; 并不等于10; 同一程序運(yùn)行不同結(jié)果,這是不允許的
此時(shí)就要借助于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(); }
當(dāng)你在一個(gè)線程中調(diào)用lock()時(shí),其他線程會(huì)試圖在同一個(gè)地方調(diào)用lock(),直到獲得鎖的線程調(diào)用unlock()。 lock()的一個(gè)非阻塞替代是tryLock()。
QMutex在非競爭情況下進(jìn)行了優(yōu)化。 如果該互斥體沒有爭用,則非遞歸QMutex將不分配內(nèi)存。 它的構(gòu)建和銷毀幾乎沒有開銷,這意味著有很多互斥體作為其他類的一部分是很好的。
當(dāng)線程1被cpu延時(shí)處理,而線程2處理到method2()時(shí)自動(dòng)會(huì)進(jìn)入method1()繼續(xù)處理number /=4;再回到method2();而此時(shí)如果線程1繼續(xù)執(zhí)行時(shí),自動(dòng)又會(huì)進(jìn)入到method2();
4.QThread 啟動(dòng)暫停等待信號(hào)與槽控制實(shí)例
延續(xù)控制臺(tái)線程例子 在每個(gè)線程后面加上 thread1->wait(); qDebug() << "hello world!";
預(yù)期的結(jié)果將會(huì)是, 在線程輸出完后才會(huì)輸出hello world!
#include <QCoreApplication> #include "mythread.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); //連續(xù)創(chuàng)建三個(gè)子線程 myThread *thread1 = new myThread; thread1->name = "mythred1"; thread1->start(); thread1->wait(); qDebug() << "hello world!"; return exec(); }
現(xiàn)在轉(zhuǎn)到GUI下,下面一個(gè)例子:
//自定義線程類,頭文件 #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(); }
最終結(jié)果:
當(dāng)點(diǎn)擊start 開啟線程 stop 停止線程 通過顯號(hào)與槽顯示結(jié)果
然而方法一Thread線程繼承QThread方式,在實(shí)際問題中卻有著很多的問題如下文簡介:早在2006年已經(jīng)被qt工程師提出;(更直指此方法是錯(cuò)誤的用法)
我們(Qt用戶)正廣泛地使用IRC來進(jìn)行交流。我在Freenode網(wǎng)站掛出了#qt標(biāo)簽,用于幫助大家解答問題。我經(jīng)??吹降囊粋€(gè)問題(這讓我不厭其煩),是關(guān)于理解Qt的線程機(jī)制以及如何讓他們寫的相關(guān)代碼正確工作。人們貼出他們的代碼,或者用代碼寫的范例,而我則總是以這樣的感觸告終:
你們都用錯(cuò)了!
我覺得有件重要的事情得澄清一下,也許有點(diǎn)唐突了,然而,我不得不指出,下面的這個(gè)(假想中的)類是對面向?qū)ο笤瓌t的錯(cuò)誤應(yīng)用,同樣也是對Qt的錯(cuò)誤應(yīng)用。
class MyThread : public QThread { public: MyThread() { moveToThread(this); } void run(); signals: void progress(int); void dataReady(QByteArray); public slots: void doWork(); void timeoutHandler(); };
我對這份代碼最大的質(zhì)疑在于 moveToThread(this); 我見過太多人這么使用,并且完全不明白它做了些什么。那么你會(huì)問,它究竟做了什么?moveToThread()函數(shù)通知Qt準(zhǔn)備好事件處理程序,讓擴(kuò)展的信號(hào)(signal)和槽(slot)在指定線程的作用域中調(diào)用。QThread是線程的接口,所以我們是在告訴這個(gè)線程在“它內(nèi)部”執(zhí)行代碼。我們也應(yīng)該在線程運(yùn)行之前做這些事。即使這份代碼看起來可以運(yùn)行,但它很混亂,并不是QThread設(shè)計(jì)中的用法(QThread中寫的所有函數(shù)都應(yīng)該在創(chuàng)建它的線程中調(diào)用,而不是QThread開啟的線程)。
在我的印象中,moveToThread(this); 是因?yàn)槿藗冊谀承┪恼轮锌吹讲⑶沂褂枚鱾鏖_來的。一次快速的網(wǎng)絡(luò)搜索就能找到此類文章,所有這些文章中都有類似如下情形的段落:
- 繼承QThread類
- 添加用來進(jìn)行工作的信號(hào)和槽
- 測試代碼,發(fā)現(xiàn)槽函數(shù)并沒有在“正確的線程”中執(zhí)行
- 谷歌一下,發(fā)現(xiàn)了moveToThread(this); 然后寫上“看起來的確管用,所以我加上了這行代碼”
我認(rèn)為,這些都源于第一步。QThread是被設(shè)計(jì)來作為一個(gè)操作系統(tǒng)線程的接口和控制點(diǎn),而不是用來寫入你想在線程里執(zhí)行的代碼的地方。我們(面向?qū)ο蟪绦騿T)編寫子類,是因?yàn)槲覀兿霐U(kuò)充或者特化基類中的功能。我唯一想到的繼承QThread類的合理原因,是添加QThread中不包含的功能,比如,也許可以提供一個(gè)內(nèi)存指針來作為線程的堆棧,或者可以添加實(shí)時(shí)的接口和支持。用于下載文件、查詢數(shù)據(jù)庫,或者做任何其他操作的代碼都不應(yīng)該被加入到QThread的子類中;它應(yīng)該被封裝在它自己的對象中。
通常,你可以簡單地把類從繼承QThread改為繼承QObject,并且,也許得修改下類名。QThread類提供了start()信號(hào),你可以將它連接到你需要的地方來進(jìn)行初始化操作。為了讓你的代碼實(shí)際運(yùn)行在新線程的作用域中,你需要實(shí)例化一個(gè)QThread對象,并且使用moveToThread()函數(shù)將你的對象分配給它。你同過moveToThread()來告訴Qt將你的代碼運(yùn)行在特定線程的作用域中,讓線程接口和代碼對象分離。如果需要的話,現(xiàn)在你可以將一個(gè)類的多個(gè)對象分配到一個(gè)線程中,或者將多個(gè)類的多個(gè)對象分配到一個(gè)線程。換句話說,將一個(gè)實(shí)例與一個(gè)線程綁定并不是必須的。
我已經(jīng)聽到了許多關(guān)于編寫Qt多線程代碼時(shí)過于復(fù)雜的抱怨。原始的QThread類是抽象類,所以必須進(jìn)行繼承。但到了Qt4.4不再如此,因?yàn)镼Thread::run()有了一個(gè)默認(rèn)的實(shí)現(xiàn)。在之前,唯一使用QThread的方式就是繼承。有了線程關(guān)聯(lián)性的支持,和信號(hào)槽連接機(jī)制的擴(kuò)展,我們有了一種更為便利地使用線程的方式。我們喜歡便利,我們想使用它。不幸的是,我太晚地意識(shí)到之前迫使人們繼承QThread的做法讓新的方式更難普及。
我也聽到了一些抱怨,是關(guān)于沒有同步更新范例程序和文檔來向人們展示如何用最不令人頭疼的方式便利地進(jìn)行開發(fā)的。如今,我能引用的最佳的資源是我數(shù)年前寫的一篇博客。()
免責(zé)聲明:你所看到的上面的一切,當(dāng)然都只是個(gè)人觀點(diǎn)。我在這些類上面花費(fèi)了很多精力,因此關(guān)于要如何使用和不要如何使用它們,我有著相當(dāng)清晰的想法。
譯者注:
最新的Qt幫助文檔同時(shí)提供了建立QThread實(shí)例和繼承QThread的兩種多線程實(shí)現(xiàn)方式。根據(jù)文檔描述和范例代碼來看,若想在子線程中使用信號(hào)槽機(jī)制,應(yīng)使用分別建立QThread和對象實(shí)例的方式;若只是單純想用子線程運(yùn)行阻塞式函數(shù),則可繼承QThread并重寫QThread::run()函數(shù)。
由于繼承QThread后,必須在QThread::run()函數(shù)中顯示調(diào)用QThread::exec()來提供對消息循環(huán)機(jī)制的支持,而QThread::exec()本身會(huì)阻塞調(diào)用方線程,因此對于需要在子線程中使用信號(hào)槽機(jī)制的情況,并不推薦使用繼承QThread的形式,否則程序編寫會(huì)較為復(fù)雜。
從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函數(shù)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(); }
總結(jié):
繼承QThread老式方法
1.定義繼承QThread的類A 復(fù)寫run()函數(shù);
2.在主線程中實(shí)例化A對象a
3.通過調(diào)用a->start()啟動(dòng)線程,線程會(huì)自動(dòng)調(diào)用run()虛函數(shù);run不可直接調(diào)用;
新方法:
1.創(chuàng)建繼承Obeject的類A 將要在線程中實(shí)現(xiàn)的方法在A類中實(shí)現(xiàn)
2.在主線程中實(shí)例化A對象a,再實(shí)例化QThread類對象b
3.通過a.moveToThread(&b);將a對象的實(shí)現(xiàn)移入線程b對象作用范圍內(nèi)運(yùn)行
4.b->start()啟動(dòng)線程;
5.通過信號(hào)與槽的方式啟動(dòng)調(diào)用A類成員函數(shù);
常用函數(shù):
- QThread類
- start(),//啟動(dòng)線程;
- wait()//等待線程運(yùn)行結(jié)束;
- quit(),//線程運(yùn)行結(jié)束退出線程
線程與進(jìn)程區(qū)別:
進(jìn)程是系統(tǒng)為每個(gè)程序分配有獨(dú)立運(yùn)行空間的運(yùn)行實(shí)例
線程是與進(jìn)程共用內(nèi)存空間的一個(gè)獨(dú)立運(yùn)行實(shí)例;相對而言線程比進(jìn)程的消耗更低;
結(jié)語:
新版qt5的主要目的也就是讓每個(gè)線程能獨(dú)立運(yùn)行在其線程作用域中,線程與線程之前的交互則通過connect機(jī)制;因此對于需要在子線程中使用信號(hào)槽機(jī)制的情況,并不推薦使用繼承QThread的形式;些方式僅實(shí)用于在只需要在run()中運(yùn)行一些簡單的函數(shù);
到此這篇關(guān)于QT5 Thread線程的具體實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)QT5 Thread線程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實(shí)現(xiàn)LeetCode(2.兩個(gè)數(shù)字相加)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(兩個(gè)數(shù)字相加),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07舉例講解C語言程序中對二叉樹數(shù)據(jù)結(jié)構(gòu)的各種遍歷方式
這篇文章主要介紹了舉例講解C語言程序中對二叉樹數(shù)據(jù)結(jié)構(gòu)的各種遍歷方式,先序中序后序二叉樹遍歷幾乎成了最老生常談的數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)知識(shí),的朋友可以參考下2016-04-04c++初級(jí)并查集知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給各位分享的是關(guān)于c++初級(jí)并查集知識(shí)點(diǎn)以及實(shí)例代碼內(nèi)容,有需要的朋友們學(xué)習(xí)下。2019-07-07