1 簡介
參考視頻:https://www.bilibili.com/video/BV1XW411x7NU?p=74
使用多線程的好處:提高應用程序響應速度、使多CPU更加高效、改善程序結構。
在Qt中使用QThread來管理線程。Qt中使用線程時,需要自己實現一個thread的類。
多線程使用過程中注意事項:
1)線程不能操作UI對象(從Qwidget直接或間接派生的窗口對象)
2)需要移動到子線程中處理的模塊類,創建的對象的時候不能指定父對象。
2 測試說明
(1)基本使用
功能說明如下:
工程文件有:
mythread.h和mythread.cpp是自定義的線程類,需要改為繼承自QThread,QThread類有一個虛函數run(),它就是線程處理函數,我們需要重寫它。
當我們調用QThread的start()函數時,會間接的調用run()函數。
widget.h和widget.cpp是主窗口的代碼。
mythread.h的代碼:

1 #ifndef MYTHREAD_H 2 #define MYTHREAD_H 3 4 #include <QObject> 5 #include <QThread> 6 7 class MyThread : public QThread 8 { 9 Q_OBJECT 10 public: 11 explicit MyThread(QObject *parent = nullptr); 12 13 signals: 14 void isDone(); 15 16 protected: 17 //QThread的虛函數,線程處理函數 18 //不能直接調用,通過start()間接調用 19 void run(); 20 21 public slots: 22 }; 23 24 #endif // MYTHREAD_H
mythread.cpp代碼:

1 #include "mythread.h" 2 3 MyThread::MyThread(QObject *parent) : QThread(parent) 4 { 5 6 } 7 8 void MyThread::run() 9 { 10 //很復雜的數據處理,需要耗時5s 11 sleep(5); 12 //發送處理完成信號 13 emit isDone(); 14 }
widget.h代碼:

1 #ifndef WIDGET_H 2 #define WIDGET_H 3 4 #include <QWidget> 5 #include <QTimer> //定時器 6 #include "mythread.h" //線程 7 8 namespace Ui { 9 class Widget; 10 } 11 12 class Widget : public QWidget 13 { 14 Q_OBJECT 15 16 public: 17 explicit Widget(QWidget *parent = 0); 18 ~Widget(); 19 20 void dealTimeout(); //定時器處理函數 21 void dealThread(); //處理子線程發來的信號 22 void stopThread(); //停止線程 23 24 private slots: 25 void on_pushButton_start_clicked(); 26 27 private: 28 Ui::Widget *ui; 29 30 QTimer *timer = NULL; 31 MyThread *thread = NULL; 32 }; 33 34 #endif // WIDGET_H
widget.cpp代碼:

1 #include "widget.h" 2 #include "ui_widget.h" 3 #include <QThread> 4 #include <QDebug> 5 6 Widget::Widget(QWidget *parent) : 7 QWidget(parent), 8 ui(new Ui::Widget) 9 { 10 ui->setupUi(this); 11 12 timer = new QTimer(this); 13 //分配空間 14 thread = new MyThread(this); 15 16 //只要定時器啟動,自動觸發timerout()信號 17 connect(timer, &QTimer::timeout, this, &Widget::dealTimeout); 18 //接收子線程發送的isDone信號並處理 19 connect(thread, &MyThread::isDone, this, &Widget::dealThread); 20 //當按窗口右上角x時(關閉窗口),觸發 21 connect(this, &Widget::destroyed, this, &Widget::stopThread); 22 } 23 24 Widget::~Widget() 25 { 26 delete ui; 27 } 28 29 void Widget::dealTimeout() 30 { 31 static int i = 0; 32 i++; 33 //設定lcd的值 34 ui->lcdNumber->display(i); 35 } 36 37 void Widget::dealThread() 38 { 39 //處理完數據后,關閉定時器 40 timer->stop(); 41 qDebug() << "timer turn off!!!"; 42 } 43 44 void Widget::stopThread() 45 { 46 //停止線程 47 thread->quit(); 48 //等待線程處理完事情 49 thread->wait(); 50 } 51 52 void Widget::on_pushButton_start_clicked() 53 { 54 if (timer->isActive() == false) { 55 timer->start(100); 56 } 57 //啟動線程,處理數據 58 thread->start(); 59 }
運行測試:
可以看到計數了45次之后(每次100ms),定時器停止了,表示接收到了線程發送的信號(線程睡眠5s之后發出的)。可能會有疑問為什么不是50次(剛好5s),那是因為我們先啟動定時器,在去啟動線程,這個過程需要花費時間。
(2)多線程的使用
多線程的實現模型如下,記下來就好了:
先給出實現的代碼,后面再介紹。
工程文件有:
mythread.h和mythread.cpp是自定義的線程類,繼承自QObject,和前一個例子不一樣。
widget.h和widget.cpp是主窗口的代碼。
mythread.h代碼:

1 #ifndef MYTHREAD_H 2 #define MYTHREAD_H 3 4 #include <QObject> 5 6 class MyThread : public QObject 7 { 8 Q_OBJECT 9 public: 10 explicit MyThread(QObject *parent = nullptr); 11 12 //線程處理函數 13 void myTimerout(); 14 //設置flag,用於判斷是否結束線程處理函數的while循環 15 void setFlag(bool flag = true); 16 17 signals: 18 void mySignal(); 19 20 public slots: 21 22 private: 23 bool isStop; 24 }; 25 26 #endif // MYTHREAD_H
mythread.c代碼:

1 #include "mythread.h" 2 #include <QThread> 3 #include <QDebug> 4 5 MyThread::MyThread(QObject *parent) : QObject(parent) 6 { 7 isStop = false; 8 } 9 10 void MyThread::myTimerout() 11 { 12 while (isStop == false) { 13 QThread::sleep(1); 14 emit mySignal(); 15 qDebug() << "子線程號:" << QThread::currentThread(); 16 if (true == isStop) { 17 break; 18 } 19 } 20 } 21 22 void MyThread::setFlag(bool flag) 23 { 24 isStop = flag; 25 }
widget.h代碼:

1 #ifndef WIDGET_H 2 #define WIDGET_H 3 4 #include <QWidget> 5 #include "mythread.h" 6 #include <QThread> 7 8 namespace Ui { 9 class Widget; 10 } 11 12 class Widget : public QWidget 13 { 14 Q_OBJECT 15 16 public: 17 explicit Widget(QWidget *parent = 0); 18 ~Widget(); 19 20 void dealsignal(); 21 void dealclose(); 22 signals: 23 //啟動子線程的信號 24 void startThreadSignal(); 25 26 private slots: 27 void on_pushButton_start_clicked(); 28 29 void on_pushButton_stop_clicked(); 30 31 private: 32 Ui::Widget *ui; 33 MyThread *mythread = NULL; 34 QThread *thread = NULL; 35 }; 36 37 #endif // WIDGET_H
widget.cpp代碼:

1 #include "widget.h" 2 #include "ui_widget.h" 3 #include <QDebug> 4 5 Widget::Widget(QWidget *parent) : 6 QWidget(parent), 7 ui(new Ui::Widget) 8 { 9 ui->setupUi(this); 10 //動態分配空間,不能指定父對象 11 mythread = new MyThread; 12 //創建子線程 13 thread = new QThread(this); 14 //把自定義的線程加入到子線程中 15 mythread->moveToThread(thread); 16 17 //處理子線程發送的信號 18 connect(mythread, &MyThread::mySignal, this, &Widget::dealsignal); 19 qDebug() << "主線程號:" << QThread::currentThread(); 20 //發送信號給子線程,通過信號和槽調用子線程的線程處理函數 21 connect(this, &Widget::startThreadSignal, mythread, &MyThread::myTimerout); 22 //關閉主窗口 23 connect(this, &Widget::destroyed, this, &Widget::dealclose); 24 } 25 26 Widget::~Widget() 27 { 28 delete ui; 29 } 30 31 void Widget::dealsignal() 32 { 33 static int i = 0; 34 i++; 35 ui->lcdNumber->display(i); 36 } 37 38 void Widget::dealclose() 39 { 40 mythread->setFlag(true); 41 thread->quit(); 42 thread->wait(); 43 delete mythread; 44 } 45 46 void Widget::on_pushButton_start_clicked() 47 { 48 if (thread->isRunning() == true) { 49 return; 50 } 51 //啟動線程,但是沒有啟動線程處理函數 52 thread->start(); 53 mythread->setFlag(false); 54 //不能直接調用線程處理函數 55 //直接調用導致線程處理函數和主線程在同一個線程 56 //只能通過信號和槽調用 57 emit startThreadSignal(); 58 } 59 60 void Widget::on_pushButton_stop_clicked() 61 { 62 if (thread->isRunning() == false) { 63 return; 64 } 65 mythread->setFlag(true); 66 thread->quit(); 67 thread->wait(); 68 }
需要說明以下幾點:
(1)創建自定義線程變量時,不能指定父對象(mythread=newMyThread;),因為調用moveToThread函數之后,變量在創建的線程中使用回收,而不是在主線程。
(2)子線程會睡眠1s就發送一個信號mySignal給主線程,主線程接收信號,在槽函數中將數值累加一,並在LCD上顯示。
(3)主線程通過信號和槽的方式調用子線程處理函數,主線程發送信號給子線程,槽函數就是子線程的線程處理函數。
(4)子線程setFlag()的作用是:關閉子線程時,quit()函數會等待子線程執行結束之后再回收,但是如果不設置標志,while循環會一直執行,子線程也就沒有結束。
運行測試:
注意看打印的信息,可以看到子線程和主線程的id。
(3)線程繪圖
功能:子線程在處理函數中繪制圖像,然后通過信號把繪制的圖像傳給主線程,主線程接收到圖像之后調用update()函數更新繪圖事件,進行圖像的繪制。
也就是子線程把圖片給畫好了,傳給主線程,主線程在窗口中繪制出來。主窗口中有一個按鈕,按一次,繪制一次圖像。
工程文件有:
mythread.h和mythread.cpp是自定義的線程類;widget.h和widget.cpp是主窗口的代碼。
mythread.h代碼:

1 #ifndef MYTHREAD_H 2 #define MYTHREAD_H 3 4 #include <QObject> 5 #include <QThread> 6 #include <QImage> 7 #include <QPainter> 8 9 class MyThread : public QObject 10 { 11 Q_OBJECT 12 public: 13 explicit MyThread(QObject *parent = nullptr); 14 //線程處理函數 15 void drawImage(); 16 17 signals: 18 void updateImage(QImage image); 19 20 public slots: 21 }; 22 23 #endif // MYTHREAD_H
mythread.c代碼:

1 #include "mythread.h" 2 #include <QPoint> 3 #include <QPen> 4 #include <QBrush> 5 6 MyThread::MyThread(QObject *parent) : QObject(parent) 7 { 8 9 } 10 11 void MyThread::drawImage() 12 { 13 //繪圖設備 14 QImage image(500, 500, QImage::Format_ARGB32); 15 //定義畫家,指定繪圖設別 16 QPainter painter(&image); 17 18 //畫筆 19 QPen pen; 20 pen.setWidth(5); //設定畫筆的寬度 21 //把畫筆交給畫家 22 painter.setPen(pen); 23 //定義畫刷 24 QBrush brush; 25 brush.setStyle(Qt::SolidPattern); //設定樣式 26 brush.setColor(Qt::green); //設定顏色 27 //把畫刷交給畫家 28 painter.setBrush(brush); 29 30 //定義五個點 31 QPoint a[] = { 32 QPoint(qrand()%500, qrand()%500), 33 QPoint(qrand()%500, qrand()%500), 34 QPoint(qrand()%500, qrand()%500), 35 QPoint(qrand()%500, qrand()%500), 36 QPoint(qrand()%500, qrand()%500) 37 }; 38 //畫多邊形 39 painter.drawPolygon(a, 5); 40 //通過信號發送圖片 41 emit updateImage(image); 42 }
widget.h代碼:

1 #ifndef WIDGET_H 2 #define WIDGET_H 3 4 #include <QWidget> 5 #include <QImage> 6 #include "mythread.h" 7 #include <QThread> 8 9 namespace Ui { 10 class Widget; 11 } 12 13 class Widget : public QWidget 14 { 15 Q_OBJECT 16 17 public: 18 explicit Widget(QWidget *parent = 0); 19 ~Widget(); 20 //重寫繪圖事件 21 void paintEvent(QPaintEvent *event); 22 void getImage(QImage tmp); //槽函數 23 void dealClose(); 24 25 private: 26 Ui::Widget *ui; 27 QImage image; 28 MyThread *mythread = NULL; //自定義線程 29 QThread *thread = NULL; 30 }; 31 32 #endif // WIDGET_H
widget.cpp代碼:

1 #include "widget.h" 2 #include "ui_widget.h" 3 #include <QPainter> 4 #include <QThread> 5 6 Widget::Widget(QWidget *parent) : 7 QWidget(parent), 8 ui(new Ui::Widget) 9 { 10 ui->setupUi(this); 11 12 //自定義類對象 13 mythread = new MyThread; 14 //創建子線程 15 thread = new QThread(this); 16 //把自定義線程添加到子線程 17 mythread->moveToThread(thread); 18 //啟動子線程,但是沒有啟動線程處理函數 19 thread->start(); 20 //線程處理函數必須通過signal-slot調用 21 connect(ui->pushButton, &QPushButton::clicked, mythread, &MyThread::drawImage); 22 23 connect(mythread, &MyThread::updateImage, this, &Widget::getImage); 24 25 connect(this, &Widget::destroyed, this, &Widget::dealClose); 26 } 27 28 void Widget::getImage(QImage tmp) 29 { 30 image = tmp; 31 //更新窗口,間接調用paintEvent 32 update(); 33 } 34 35 void Widget::paintEvent(QPaintEvent *event) 36 { 37 //創建畫家,指定繪圖設備為窗口 38 QPainter painter(this); 39 painter.drawImage(50, 50, image); 40 } 41 42 void Widget::dealClose() 43 { 44 //退出子線程 45 thread->quit(); 46 //回收資源 47 thread->wait(); 48 delete mythread; 49 } 50 51 Widget::~Widget() 52 { 53 delete ui; 54 }
運行測試: