Qt修煉手冊11_多執行緒程式設計和QThread類
1.事件迴圈
學習QT多執行緒程式設計之前,有必要先熟悉事件迴圈的概念。 先看一個單執行緒介面程式的主函式程式碼: int main(int argc, char* argv[]) { QApplication app(argc, argv); // 構造主視窗物件並顯示 MainWindow w; w.show(); // 進入事件迴圈 return app.exec(); } 在程式初始化完成後,主執行緒進入main()函式開始執行應用程式碼。一般地,我們在主執行緒上構建介面物件,然後進入事件迴圈以處理控制元件繪製、使用者輸入、系統輸出等訊息。這就是我們通常說的事件驅動模型。 這裡需要提一個問題,為什麼需要多執行緒程式設計? 多執行緒程式設計旨在提高人機互動的感受。主執行緒承擔著使用者互動的重任,當在主執行緒上執行費時的程式碼時,就會影響使用者的正常操作。所以我們常把一些費時費力的計算工作移出主執行緒,開闢新的執行緒來執行之。 QThread是QT中用於執行緒管理的類,呼叫一個QThread物件的start()方法時,會建立一個新的執行緒並執行它的run()方法。預設地,run()會呼叫exec()方法進入自己的訊息迴圈中。如下圖所示:
上圖中有主執行緒、工作執行緒都是執行事件迴圈,並且注意到主執行緒內部含有thr、w、objs這些QObject物件(這些物件都是在主執行緒上建立的)。主執行緒的事件迴圈負責檢測這些物件是否有訊息要處理,有的話則呼叫物件的slot方法。可以使用QObject::moveToThread方法將某個物件移到其他執行緒中,譬如: class Worker : public QObject { Q_OBJECT … } void someFunc() { QThread thr = new QThread; Worker worker = new Worker; worker->moveToThread(thr); thr->start(); … } 如果在主執行緒上呼叫someFunc(),則thr和worker在建立後關聯在主執行緒上,當呼叫worker-> moveToThread()後,worker物件關聯到了新的執行緒中,如圖所示:
2.QThread類 2.1 類基礎
QThread類可以不受平臺影響而實現執行緒。QThread提供在程式中可以控制和管理現成的多種成員函式和訊號/槽。通過QThread類的成員函式start()啟動執行緒。 class Worker : public QObject { Q_OBJECT public slots: void doWork() { ...... } }; void MyObject::putWorkerInThread() { Worker *worker = new Worker; QThread *workerThread = new QThread(this); connect(workerThread, &QThread::started, worker, &Worker::doWork); connect(workerThread, &QThread::finished,worker, &Worker::deleteLater); worker->moveToThread(workerThread); //開始進行事件迴圈,併發射訊號workerThread->start(); workerThread->start(); } 上述程式碼中,Worker類的槽與啟動分離執行緒時傳送的訊號、執行緒終止時傳送的訊號相關聯。如果我們啟動了執行緒,就會呼叫Worker類的槽函式doWork()。
換言之,如果執行putWorkerInThread()的函式start(),那麼就相當於傳送了執行緒的啟動訊號(QThread::started),根據定義的訊號-槽,程式就會呼叫與函式connect()相關聯的Worker類的槽函式doWorker()。如果結束執行緒,則自動傳送QThread::finished訊號,傳送此訊號後,立即釋放Worker使用過的執行緒記憶體區域。
QThread通過訊號函式started()和finished()通知開始和結束,並檢視執行緒狀態。可以確認究竟是使用函式isfinished()訊號終止執行緒,還是使用函式isRunning()啟動執行緒。使用函式exit()和quit()可以結束執行緒。 2.2 多執行緒初步
如果使用多執行緒,有時需要等到所有執行緒終止。此時,使用函式wait()即可。執行緒中,使用成員函式sleep()、msleep()、usleep()可以暫停秒、毫秒及微秒單位的執行緒。 3.QThread使用的記憶體區域
QThread使用的記憶體區域分為執行緒私有區域和共享記憶體區域。執行緒內部使用的暫存器區域只能在執行緒內部共享。共享資料區域可以訪問其他執行緒。 執行緒內部使用的暫存器和棧區域如下圖所示:
執行緒內部共享區域雖然不能訪問其他執行緒,但棧區域可以線上程間共享。因此,如果實現多種執行緒訪問棧區域,需要注意互斥體,讀寫鎖等執行緒的安全性。 3.多執行緒程式設計例項與解析
widget.h #ifndef WIDGET_H #define WIDGET_H #include <QtWidgets/QWidget> #include "ui_widget.h" #include <QThread> #include <QPushButton> #include <QMutex> class MyThread : public QThread { Q_OBJECT public: MyThread(int num); private: bool threadStop; int number; QMutex mutex; public: void stop(); protected: void run(); }; ///////////////////////////////////////// class widget : public QWidget { Q_OBJECT public: widget(QWidget *parent = 0); ~widget(); MyThread *thread1; MyThread *thread2; private slots: void btn_start(); void btn_stop(); void btn_isRunning(); void btn_isFinished(); private: Ui::widgetClass ui; }; #endif // WIDGET_H widegt.cpp #include "widget.h" MyThread::MyThread(int num) { number = num; } void MyThread::stop() { threadStop = true; qDebug("[%d] Thread stop", number); } void MyThread::run() { threadStop = false; int i = 0; while(!threadStop) { mutex.lock(); qDebug("[%d] MyThread %d", number, i); i++; sleep(1); mutex.unlock(); } } /////////////////////////////////////// widget::widget(QWidget *parent) : QWidget(parent) { ui.setupUi(this); thread1 = new MyThread(1); thread2 = new MyThread(2); QPushButton *btn_start = new QPushButton("START", this); btn_start->setGeometry(10, 10, 80, 40); QPushButton *btn_stop = new QPushButton("STOP", this); btn_stop->setGeometry(100, 10, 80, 40); QPushButton *btn_isRunning = new QPushButton("IsRunning", this); btn_isRunning->setGeometry(200, 10, 100, 40); QPushButton *btn_isFinished = new QPushButton("IsFinished", this); btn_isFinished->setGeometry(310, 10, 100, 40); connect(btn_start, SIGNAL(clicked()), this, SLOT(btn_start())); connect(btn_stop, SIGNAL(clicked()), this, SLOT(btn_stop())); connect(btn_isRunning, SIGNAL(clicked()), this, SLOT(btn_isRunning())); connect(btn_isFinished, SIGNAL(clicked()), this, SLOT(btn_isFinished())); } void widget::btn_start() { thread1->start(); thread2->start(); } void widget::btn_stop() { thread1->stop(); thread2->stop(); } void widget::btn_isRunning() { if(thread1->isRunning()) qDebug("[1] Thread is running"); else qDebug("[1] Thread is not running"); if(thread2->isRunning()) qDebug("[2] Thread is running"); else qDebug("[2] Thread is not running"); } void widget::btn_isFinished() { if(thread1->isFinished()) qDebug("[1] Thread Finish"); else qDebug("[1] Thread not Finish"); if(thread2->isFinished()) qDebug("[2] Thread Finish"); else qDebug("[2] Thread not Finish"); } widget::~widget() {} 輸出結果:
程式碼分析: 1.QThread訊號與槽 訊號:終止執行緒例項執行傳送訊號(void finished)、啟動執行緒例項傳送訊號(void started)、結束執行緒例項傳送訊號(void terminated) 槽:執行緒中止執行槽(finished)、執行緒啟動槽(start)、執行緒結束槽(terminate) 2.QThread優先順序 QThread通過函式setPriority()設定優先順序。 4.關於QDebug的一點思考
VS2010開發Qt,怎麼顯示qDebug資訊(新增DOS視窗):
--------------------- 作者:沈子恆 來源:CSDN 原文:https://blog.csdn.net/shenziheng1/article/details/60873227?utm_source=copy 版權宣告:本文為博主原創文章,轉載請附上博文連結!