1. 程式人生 > 實用技巧 >Qt-執行緒的使用

Qt-執行緒的使用

1 簡介

參考視訊:https://www.bilibili.com/video/BV1XW411x7NU?p=74

使用多執行緒的好處:提高應用程式響應速度、使多CPU更加高效、改善程式結構。

在Qt中使用QThread來管理執行緒。Qt中使用執行緒時,需要自己實現一個thread的類。

2 測試說明

(1)基本使用

功能說明如下:

工程檔案有:

mythread.h和mythread.cpp是自定義的執行緒類,需要改為繼承自QThread,QThread類有一個虛擬函式run(),它就是執行緒處理函式,我們需要重寫它。

當我們呼叫QThread的start()函式時,會間接的呼叫run()函式。

widget.h和widget.cpp是主視窗的程式碼。

mythread.h的程式碼:

 #ifndef MYTHREAD_H
#define MYTHREAD_H #include <QObject>
#include <QThread> class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr); signals:
void isDone(); protected:
//QThread的虛擬函式,執行緒處理函式
//不能直接呼叫,通過start()間接呼叫
void run(); public slots:
}; #endif // MYTHREAD_H

mythread.cpp程式碼:

 #include "mythread.h"

 MyThread::MyThread(QObject *parent) : QThread(parent)
{ } void MyThread::run()
{
//很複雜的資料處理,需要耗時5s
sleep();
//傳送處理完成訊號
emit isDone();
}

widget.h程式碼:

 #ifndef WIDGET_H
#define WIDGET_H #include <QWidget>
#include <QTimer> //定時器
#include "mythread.h" //執行緒 namespace Ui {
class Widget;
} class Widget : public QWidget
{
Q_OBJECT public:
explicit Widget(QWidget *parent = );
~Widget(); void dealTimeout(); //定時器處理函式
void dealThread(); //處理子執行緒發來的訊號
void stopThread(); //停止執行緒 private slots:
void on_pushButton_start_clicked(); private:
Ui::Widget *ui; QTimer *timer = NULL;
MyThread *thread = NULL;
}; #endif // WIDGET_H

widget.cpp程式碼:

 #include "widget.h"
#include "ui_widget.h"
#include <QThread>
#include <QDebug> Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this); timer = new QTimer(this);
//分配空間
thread = new MyThread(this); //只要定時器啟動,自動觸發timerout()訊號
connect(timer, &QTimer::timeout, this, &Widget::dealTimeout);
//接收子執行緒傳送的isDone訊號並處理
connect(thread, &MyThread::isDone, this, &Widget::dealThread);
//當按視窗右上角x時(關閉視窗),觸發
connect(this, &Widget::destroyed, this, &Widget::stopThread);
} Widget::~Widget()
{
delete ui;
} void Widget::dealTimeout()
{
static int i = ;
i++;
//設定lcd的值
ui->lcdNumber->display(i);
} void Widget::dealThread()
{
//處理完資料後,關閉定時器
timer->stop();
qDebug() << "timer turn off!!!";
} void Widget::stopThread()
{
//停止執行緒
thread->quit();
//等待執行緒處理完事情
thread->wait();
} void Widget::on_pushButton_start_clicked()
{
if (timer->isActive() == false) {
timer->start();
}
//啟動執行緒,處理資料
thread->start();
}

執行測試:

可以看到計數了45次之後(每次100ms),定時器停止了,表示接收到了執行緒傳送的訊號(執行緒睡眠5s之後發出的)。可能會有疑問為什麼不是50次(剛好5s),那是因為我們先啟動定時器,在去啟動執行緒,這個過程需要花費時間。

(2)多執行緒的使用

多執行緒的實現模型如下,記下來就好了:

先給出實現的程式碼,後面再介紹。

工程檔案有:

mythread.h和mythread.cpp是自定義的執行緒類,繼承自QThread,和前一個例子不一樣。

widget.h和widget.cpp是主視窗的程式碼。

mythread.h程式碼:

 #ifndef MYTHREAD_H
#define MYTHREAD_H #include <QObject> class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr); //執行緒處理函式
void myTimerout();
//設定flag,用於判斷是否結束執行緒處理函式的while迴圈
void setFlag(bool flag = true); signals:
void mySignal(); public slots: private:
bool isStop;
}; #endif // MYTHREAD_H

mythread.c程式碼:

 #include "mythread.h"
#include <QThread>
#include <QDebug> MyThread::MyThread(QObject *parent) : QObject(parent)
{
isStop = false;
} void MyThread::myTimerout()
{
while (isStop == false) {
QThread::sleep();
emit mySignal();
qDebug() << "子執行緒號:" << QThread::currentThread();
if (true == isStop) {
break;
}
}
} void MyThread::setFlag(bool flag)
{
isStop = flag;
}

widget.h程式碼:

 #ifndef WIDGET_H
#define WIDGET_H #include <QWidget>
#include "mythread.h"
#include <QThread> namespace Ui {
class Widget;
} class Widget : public QWidget
{
Q_OBJECT public:
explicit Widget(QWidget *parent = );
~Widget(); void dealsignal();
void dealclose();
signals:
//啟動子執行緒的訊號
void startThreadSignal(); private slots:
void on_pushButton_start_clicked(); void on_pushButton_stop_clicked(); private:
Ui::Widget *ui;
MyThread *mythread = NULL;
QThread *thread = NULL;
}; #endif // WIDGET_H

widget.cpp程式碼:

 #include "widget.h"
#include "ui_widget.h"
#include <QDebug> Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//動態分配空間,不能指定父物件
mythread = new MyThread;
//建立子執行緒
thread = new QThread(this);
//把自定義的執行緒加入到子執行緒中
mythread->moveToThread(thread); //處理子執行緒傳送的訊號
connect(mythread, &MyThread::mySignal, this, &Widget::dealsignal);
qDebug() << "主執行緒號:" << QThread::currentThread();
//傳送訊號給子執行緒,通過訊號和槽呼叫子執行緒的執行緒處理函式
connect(this, &Widget::startThreadSignal, mythread, &MyThread::myTimerout);
//關閉主視窗
connect(this, &Widget::destroyed, this, &Widget::dealclose);
} Widget::~Widget()
{
delete ui;
} void Widget::dealsignal()
{
static int i = ;
i++;
ui->lcdNumber->display(i);
} void Widget::dealclose()
{
mythread->setFlag(true);
thread->quit();
thread->wait();
delete mythread;
} void Widget::on_pushButton_start_clicked()
{
if (thread->isRunning() == true) {
return;
}
//啟動執行緒,但是沒有啟動執行緒處理函式
thread->start();
mythread->setFlag(false);
//不能直接呼叫執行緒處理函式
//直接呼叫導致執行緒處理函式和主執行緒在同一個執行緒
//只能通過訊號和槽呼叫
emit startThreadSignal();
} void Widget::on_pushButton_stop_clicked()
{
if (thread->isRunning() == false) {
return;
}
mythread->setFlag(true);
thread->quit();
thread->wait();
}

需要說明以下幾點:

(1)建立自定義執行緒變數時,不能指定父物件(mythread=newMyThread;),因為呼叫moveToThread函式之後,變數在建立的執行緒中使用回收,而不是在主執行緒。

(2)子執行緒會睡眠1s就傳送一個訊號mySignal給主執行緒,主執行緒接收訊號,在槽函式中將數值累加一,並在LCD上顯示。

(3)主執行緒通過訊號和槽的方式呼叫子執行緒處理函式,主執行緒傳送訊號給子執行緒,槽函式就是子執行緒的執行緒處理函式。

(4)子執行緒setFlag()的作用是:關閉子執行緒時,quit()函式會等待子執行緒執行結束之後再回收,但是如果不設定標誌,while迴圈會一直執行,子執行緒也就沒有結束。

執行測試:

注意看列印的資訊,可以看到子執行緒和主執行緒的id。