Qt-線程的使用


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
View Code

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 }
View Code

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
View Code

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 }
View Code

運行測試:

可以看到計數了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
View Code

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 }
View Code

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
View Code

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 }
View Code

需要說明以下幾點:

(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
View Code

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 }
View Code

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
View Code

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 }
View Code

運行測試:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM