QT從入門到入土(五(1))——多線程(QThread)


引言

前面幾篇已經對C++的線程做了簡單的總結,淺談C++11中的多線程(三) - 唯有自己強大 - 博客園 (cnblogs.com)。本篇着重於Qt多線程的總結與實現。

跟C++11中很像的是,Qt中使用QThread來管理線程,一個QThread對象管理一個線程,在使用上有很多跟C++11中相似的地方,但更多的是Qt中獨有的內容。另外,QThread對象也有消息循環exec()函數,即每個線程都有一個消息循環,用來處理自己這個線程的事件。


一,知識回顧

首先先來回顧一下一些知識點:

1,為什么需要多線程?

解決耗時操作堵塞整個程序的問題,一般我們會將耗時的操作放入子線程中

2,進程和線程的區別:

進程:一個獨立的程序,擁有獨立的虛擬地址空間,要和其他進程通信,需要使用進程通信的機制。

線程:沒有自己的資源,都是共享進程的虛擬地址空間,多個線程通信存在隱患。

ps:在操作系統每一個進程都擁有獨立的內存空間,線程的開銷遠小於進程,一個進程可以擁有多個線程。(因此我們常用多線程並發,而非多進程並發)

為了更容易理解多線程的作用,先看一個實例:

在主線程中運行一個10s耗時的操作。(通過按鈕來觸發)

#include "threadtest.h"
#include"qthread.h"
Threadtest::Threadtest(QWidget* parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);
    connect(ui.btn_start, &QPushButton::clicked, this, &Threadtest::on_pushButton_clicked);

}
void Threadtest::on_pushButton_clicked()
{
    QThread::sleep(10);//主線程
}

可以看到程序運行過程中,整個線程都在響應10秒的耗時操作,對於線程的消息循環exec()函數就未響應了(就是你在這個過程中拖動界面是無反應的)

二,線程類 QThread

Qt 中提供了一個線程類,通過這個類就可以創建子線程了,Qt 中一共提供了兩種創建子線程的方式,后邊會依次介紹其使用方式。先來看一下這個類中提供的一些常用 API 函數:

  • 2.1 常用共用成員函數
// QThread 類常用 API
// 構造函數
QThread::QThread(QObject *parent = Q_NULLPTR);
// 判斷線程中的任務是不是處理完畢了
bool QThread::isFinished() const;
// 判斷子線程是不是在執行任務
bool QThread::isRunning() const;

// Qt中的線程可以設置優先級
// 得到當前線程的優先級
Priority QThread::priority() const;
void QThread::setPriority(Priority priority);
優先級:
    QThread::IdlePriority        --> 最低的優先級
    QThread::LowestPriority
    QThread::LowPriority
    QThread::NormalPriority
    QThread::HighPriority
    QThread::HighestPriority
    QThread::TimeCriticalPriority
    QThread::InheritPriority    --> 最高的優先級, 默認是這個


// 退出線程, 停止底層的事件循環
// 退出線程的工作函數
void QThread::exit(int returnCode = 0);
// 調用線程退出函數之后, 線程不會馬上退出因為當前任務有可能還沒有完成, 調回用這個函數是
// 等待任務完成, 然后退出線程, 一般情況下會在 exit() 后邊調用這個函數
bool QThread::wait(unsigned long time = ULONG_MAX);
  • 2.2 信號槽
// 和調用 exit() 效果是一樣的
// 代用這個函數之后, 再調用 wait() 函數
[slot] void QThread::quit();
// 啟動子線程
[slot] void QThread::start(Priority priority = InheritPriority);
// 線程退出, 可能是會馬上終止線程, 一般情況下不使用這個函數
[slot] void QThread::terminate();

// 線程中執行的任務完成了, 發出該信號
// 任務函數中的處理邏輯執行完畢了
[signal] void QThread::finished();
// 開始工作之前發出這個信號, 一般不使用
[signal] void QThread::started();
  • 2.3靜態函數
// 返回一個指向管理當前執行線程的QThread的指針
[static] QThread *QThread::currentThread();
// 返回可以在系統上運行的理想線程數 == 和當前電腦的 CPU 核心數相同
[static] int QThread::idealThreadCount();
// 線程休眠函數
[static] void QThread::msleep(unsigned long msecs);    // 單位: 毫秒
[static] void QThread::sleep(unsigned long secs);    // 單位: 秒
[static] void QThread::usleep(unsigned long usecs);    // 單位: 微秒

 三,Qt中實現多線程的兩種方法

🧡🧡3.1.派生QThread類對象的方法(重寫Run函數)

首先,以文字形式來說明需要哪幾個步驟。

  1. 自定義一個自己的類,使其繼承自QThread類;
  2. 在自定義類中覆寫QThread類中的虛函數run()。

這很可能就是C++中多態的使用。補充一點:QThread類繼承自QObject類。

這里要重點說一下run()函數了。它作為線程的入口,也就是線程從run()開始執行,我們打算在線程中完成的工作都要寫在run()函數中,個人認為可以把run()函數理解為線程函數。這也就是子類覆寫基類的虛函數,基類QThread的run()函數只是簡單啟動exec()消息循環,關於這個exec()后面有很多東西要講,請做好准備。
那么我們就來嘗試用多線程實現10s耗時的操作:(用按鈕觸發)

  • 1️⃣在編輯好ui界面后,先創建一個workThread的類。(繼承自QThread類)

  • 2️⃣在workThread1的類中重寫run函數

在workThread.h的聲明run函數:

#include <qthread.h>
class workThread : public QThread
{
public:
    void run();
};

在workThread.cpp中重寫run函數(並打印子線程的ID):

#include "workThread.h"
#include"qdebug.h"
workThread::workThread(QObject* parent)
{

}
//重寫run函數
void workThread::run()
{
    qDebug() << "當前子線程ID:" << QThread::currentThreadId();
    qDebug() << "開始執行線程";
    QThread::sleep(10);
    qDebug() << "線程結束";

}
  • 3️⃣在主類中啟動線程

threadtest.h中聲明線程和按鈕事件

#include <QtWidgets/QMainWindow>
#include "ui_threadtest.h"
#include"workThread.h"
#pragma execution_character_set("utf-8")
class Threadtest : public QMainWindow
{
    Q_OBJECT

public:
    Threadtest(QWidget *parent = Q_NULLPTR);

    
private:
    Ui::ThreadtestClass ui;
    void btn_clicked(); workThread* thread;
};

threadtest.cpp中實現,並啟動子線程線程

#include "threadtest.h"
#include"qthread.h"
#include"qdebug.h"

Threadtest::Threadtest(QWidget* parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);
    connect(ui.btn_start, &QPushButton::clicked, this, &Threadtest::btn_clicked);
   thread = new workThread(this);  
}
void Threadtest::btn_clicked()
{
    qDebug() << "主線程id:" << QThread::currentThreadId();
    thread->start();//啟動子線程
}

可以實現,在執行耗時操作時也可拖動界面。

 

需要注意的是:

在主程序中添加workThread類的頭文件時,需要將workThread.h放在threadtest.h中(不然會報錯!!!!)

使用QThread::currentThreadId()來查看當前線程的ID,無論是子線程還是主線程,不同線程其ID是不同的。注意,這是一個靜態函數,因此可以不經過對象來調用。

創建的workThread1類的執行實際上是在主線程里的,只有run函數內的程序才會在子線程中執行!(即QThread只是線程的管理類,只有run()才是我們的線程函數)

因此在QThread(即創建的類)中的成員變量屬於主線程,在訪問前需要判斷訪問是否安全。run()中創建的變量屬於子線程。

線程之間共享內存是不安全的(由於多線程爭奪資源會影響數據安全問題),解決的辦法就是要上鎖。


關於互斥鎖

互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問。只要某一個線程上鎖了,那么就會強行霸占公共資源的訪問權,其他的線程無法訪問直到這個線程解鎖了,從而保護共享資源。

在Qt中的互斥鎖常用兩種方式:

  • QMutex類下的lock(上鎖)和unlcok(解鎖)
//需要在頭文件中引用#include<QMutex>
//並在頭文件的private中聲明QMutex mutex;


mutex.lock() public_value++;//公共成員變量 mutex.unlock();
  • QMutexLocker類下的lock(上鎖后,當執行析構函數時會自動解鎖)
//需要在頭文件中引用#include<QMutexLocker>和include<QMutex>
//並在頭文件的private中聲明QMutex mutex;

QMutexLocker lock(&mutex);//執行構造函數時執行mutex.lock()
public_value++;

//執行析構函數時執行mutex.unlock()

 


關於exec()消息循環

個人認為,exec()這個點太重要了,同時還不太容易理解。

比如下面的代碼中有兩個exec(),我們講“一山不容二虎”,放在這里就是說,一個線程中不能同時運行兩個exec(),否則就會造成另一個消息循環得不到消息。像QDialog模態窗口中的exec()就是因為在主線程中同時開了兩個exec(),導致主窗口的exec()接收不到用戶的消息了。但是!但是!但是!我們這里卻沒有任何問題,因為它們沒有出現在同一個線程中,一個是主線程中的exec(),一個是子線程中的exec()。

#include <QApplication>
#include <QThread>
#include <QDebug>
 
class MyThread:public QThread
{
    public:
        void run()
        {
            qDebug()<<"child thread begin"<<endl;
            qDebug()<<"child thread"<<QThread::currentThreadId()<<endl;
            QThread::sleep(5);
            qDebugu()<<"QThread end"<<endl;
            this->exec();
        }
};
 
int main(int argc,char ** argv) //mian()作為主線程
{
    QApplication app(argc,argv);
 
    MyThread thread; //創建一個QThread派生類對象就是創建了一個子線程
    thread.start(); //啟動子線程,然后會自動調用線程函數run()
 
    qDebug()<<"main thread"<<QThread::currentThreadId()<<endl;
    QThread::sleep(5);
    qDebugu()<<"main thread"<<QThread::currentThreadId()<<endl;
 
    thread.quit(); //使用quit()或者exit()使得子線程能夠退出消息循環,而不至於陷在子線程中
    thread.wait(); //等待子線程退出,然后回收資源
                   //thread.wait(5000); //設定等待的時間
    
    return app.exec();    
}

如果run()函數中沒有執行exec()消息循環函數,那么run()執行完了也就意味着子線程退出了。一般在子線程退出的時候需要主線程去回收資源,可以調用QThread的wait(),使主線程等待子線程退出,然后回收資源。這里wait()是一個阻塞函數,有點像C++11中的join()函數。

但是!但是!但是!run()函數中調用了exec()函數,exec()是一個消息循環,也可以叫做事件循環,也是會阻塞的,相當於一個死循環使子線程卡在這里永不退出,必須調用QThread的quit()函數或者exit()函數才可以使子線程退出消息循環,並且有時還不是馬上就退出,需要等到CPU的控制權交給線程的exec()。

所以先要thread.quit();使退出子線程的消息循環, 然后thread.wait();在主線程中回收子線程的資源。

值得注意的有兩點:子線程的exet()消息循環必須在run()函數中調用;如果沒有消息循環的話,則沒有必要調用quit( )或者exit(),因為調用了也不會起作用。

第一種創建線程的方式需要在run()中顯式調用exec(),但是exec()有什么作用呢,目前還看不出來,需要在第二種創建線程的方式中才能知道。


 💜💜3.2.使用信號與槽方式來實現多線程

 剛講完使用QThread派生類對象的方法創建線程,現在就要來說它一點壞話。這種方法存在一個局限性,只有一個run()函數能夠在線程中去運行,但是當有多個函數在同一個線程中運行時,就沒辦法了,至少實現起來很麻煩。所以,當當當當,下面將介紹第二種創建線程的方式:使用信號與槽的方式,也就是把在線程中執行的函數(我們可以稱之為線程函數)定義為一個槽函數。

仍然是首先以文字形式說明這種方法的幾個步驟。

  • 創建一個新的類(mywork),讓這個類從 QObject 派生,在這個類中添加一個公共的成員函數(working),函數體就是我們要子線程中執行的業務邏輯
class MyWork:public QObject
{
public:
    .......
    // 函數名自己指定, 叫什么都可以, 參數可以根據實際需求添加
    void working();
}
  • 在主線程中創建一個 QThread 對象,這就是子線程的對象
QThread* sub = new QThread;
  • 在主線程中創建工作的類對象(千萬不要指定給創建的對象指定父對象)
MyWork* work = new MyWork(this);    // error
MyWork* work = new MyWork;          // ok
  • 將 MyWork 對象移動到創建的子線程對象中,需要調用 QObject 類提供的 moveToThread() 方法
// void QObject::moveToThread(QThread *targetThread);
// 如果給work指定了父對象, 這個函數調用就失敗了
// 提示: QObject::moveToThread: Cannot move objects with a parent
work->moveToThread(sub);    // 移動到子線程中工作
  • 啟動子線程,調用 start(), 這時候線程啟動了,但是移動到線程中的對象並沒有工作
  • 調用 MyWork 類對象的工作函數,讓這個函數開始執行,這時候是在移動到的那個子線程中運行的

代碼實例:

  • 1️⃣創建一個workThread的類。(繼承自QThread類)

定義槽函數(子線程執行的程序都可以放在槽函數中)

//workThread.cpp(先在workThread.h中聲明槽函數)

void
workThread:: doWork() { qDebug()<<"當前線程ID:"<<QThread::currentThreadId(); qDebug()<<"開始執行"; QThread::sleep(10); qDebug()<<"結束執行"; }
  • 2️⃣主線程中分別對workThread類和QTread類實例化

在threadtest.h中聲明

#include <QtWidgets/QMainWindow>
#include "ui_threadtest.h"
#include"workThread.h"
#include"qthread.h"
#pragma execution_character_set("utf-8")
class Threadtest : public QMainWindow
{
    Q_OBJECT

public:
    Threadtest(QWidget *parent = Q_NULLPTR);  
private:
    Ui::ThreadtestClass ui;
    void btn_clicked();
 workThread* thread; //實例化workThread類
    QThread* qthread;   //實例化QThread類
};

在threadtest.cpp中實現通過moveToThread將自己放到線程QThread對象中,最后啟動線程

#include "threadtest.h"
#include"qthread.h"
#include"qdebug.h"

Threadtest::Threadtest(QWidget* parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);
   
    //不能指定自定義類的對象的父類為widget,即沒有this(很重要!!!!)
   thread = new workThread();  
   qthread1 = new QThread(this);
   thread->moveToThread(qthread1);
   //線程結束時清理線程內存
   connect(qthread1, &QThread::finished, qthread1, &QThread::deleteLater);
   //將按鈕事件(信號)綁定槽函數
   connect(ui.btn_start, &QPushButton::clicked, thread, &workThread::dowork);
   //打印主線程
   connect(ui.btn_start, &QPushButton::clicked, [=]() {
       qDebug() << "主線程id:" << QThread::currentThreadId();
       });
   //線程啟動
   qthread1->start();
}

在運行槽函數時,不能在此直接調用(如:thread1->doWork())。而是用connect連接信號和槽

這里將打印主線程和槽函數都綁定在了button按鈕的click事件上了。

特別需要注意的是(爬坑記錄):

  • 子線程中不能操作UI
Qt創建的子線程中是不能對UI對象進行任何操作的,即QWidget及其派生類對象,這個是我掉的第一個坑。可能是由於考慮到安全性的問題,所以Qt中子線程不能執行任何關於界面的處理,包括消息框的彈出。正確的操作應該是通過信號槽,將一些參數傳遞給主線程,讓主線程(也就是Controller)去處理。
  • 自定義的類不能指定父對象
比如上面程序中的:(不能指定自定義類對象為widget,即不可以加this)
thread1=new workThread1();//初始化
  • QThread和connect的關系
我們使用最多的是QThread於connect的關系,在使用connect函數的時候,我們一般會把最后一個參數忽略掉。這時候我們需要看下函數原型:
[static] QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)

可以看到,最后一個參數代表的是連接的方式。我們一般會用到方式是有三種:

自動連接(AutoConnection):默認的連接方式。如果信號與槽,也就是發送者與接受者在同一線程,等同於直接連接;如果發送者與接受者處在不同線程,等同於隊列連接。

直接連接(Direct Connection)當信號發射時,槽函數將直接被調用。無論槽函數所屬對象在哪個線程,槽函數都在發射者所在線程執行。

隊列連接(Queued Connection)當控制權回到接受者所在線程的事件循環式,槽函數被調用。槽函數在接收者所在線程執行。

因此我們需要注意的是:

  1. 主線程對象發出信號連接QThread子類的槽函數,QThread子類對象在主線程創建的,無論采用哪種連接方式,槽函數都屬在主線程調用。(如果在重寫的run函數中調用了槽函數,此時槽函數在次線程執行,注意數據安全)。
  2. 次線程run中發出信號,槽函數可以是發出信號對象自身的槽函數,自發自收,都是次線程中行。槽函數是QThread子類的槽函數,或者主線程中對象的槽函數,這里的種情況需要你指明run中connect中的連接方式,直連則該槽函數在次線程中執行(可能發生數據安全問題),列隊則在主線程執行。

🧡🧡總結一下:

  • 一定要用信號槽機制,別想着直接調用,你會發現並沒有在子線程中執行。

  • 自定義的類不能指定父對象,因為moveToThread函數會將線程對象指定為自定義的類的父對象,當自定義的類對象已經有了父對象,就會報錯。

  • 當一個變量需要在多個線程間進行訪問時,最好加上voliate關鍵字,以免讀取到的是舊的值。當然,Qt中提供了線程同步的支持,比如互斥鎖之類的玩意,使用這些方式來訪問變量會更加安全。

  • 分析發出信號的對象和接受信號對象所在的線程,再通過連接方式,判斷槽函數在哪里執行。(小白在使用中就有在run中創建對象-因為多非槽函數都需要在次線程中執行,通過指針引出來,再connect與其他模塊交互,指明連接方式為列隊形式,所以相關執行都在次線程中執行)。這里記住moveToThread只能將槽函數移到次線程中。

 

啟動多線程的操作思路

如果我們需要實現一個排序操作,即首先獲取1000個隨機數,然后用冒泡排序法對其進行排序。

方法一:重寫run函數

 思路:(構建兩個子線程,一個用於生成隨機數,一個用於冒泡排序,主線程負責調用)

  • 新建myThread類,在該類中重寫run函數(即生隨機數)。需要先接收主線程發送的要生成隨機數的個數(scvnum信號)后再進行生成,生成完成以后發送一個sendArray信號
  • 新建BubbleSort類,在該類中重寫run函數(即冒泡排序算法)。需要先接收已經生成的隨機數(rcvArray信號)后再進行排序,排序完成后發送一個finish信號
  • 主線程通過stating信號告訴myThread線程要生成的個數,然后myThread線程通過scvnum信號接收,進行生成隨機數,然后再發送一個sendArray信號(即生成隨機數集合)
  • BubbleSort子線程通過rcvArray信號接收sendArray信號(即接收隨機數)然后進行冒泡排序,再發送finish信號
  • 主線程接收到finish信號后,將排序后的隨機數顯示在界面上

實現代碼:

  • 子線程.h
#pragma once
#include"qthread.h"
#include"qvector.h"
//新建隨機數類
class myThread :
    public QThread
{
    Q_OBJECT
public:
    myThread(QObject* parent = nullptr);
    void scvnum(int num);//接收數字
protected:
    void run();
signals:
    void sendArray(QVector<int>num);//發送
private:
    int m_num;
};

//新建冒泡排序類
class BubbleSort :
    public QThread
{
    Q_OBJECT
public:
    BubbleSort(QObject* parent = nullptr);
    void rcvArray(QVector<int>list);//要接收的是排序的數
protected:
    void run();
signals:
    void finish(QVector<int>list);//排序完成后發送一個finish信號
private:
    QVector<int>m_list;
};
  • 子線程.cpp
#include "myThread.h"
#include"qelapsedtimer.h"
#include"qdebug.h"

myThread::myThread(QObject* parent) :QThread(parent)
{

}
BubbleSort::BubbleSort(QObject* parent) : QThread(parent)
{

}


void myThread::scvnum(int num)
{
    m_num = num;
}

void myThread::run()
{
    qDebug() << "child thread id" << QThread::currentThreadId();
    QVector<int> list;
    QElapsedTimer time;
    time.start();
    for (int i = 0; i < m_num; i++)
    {
        list.push_back(qrand() % 100000);
    }
    int milsec = time.elapsed();
    qDebug() << "The number of" << m_num << "generated";
    qDebug() << "shared" << milsec << "second";
    emit sendArray(list);
}

void BubbleSort::rcvArray(QVector<int> list)
{
    m_list = list;
}

void BubbleSort::run()
{
    qDebug() << "BubbleSort thread id:" << QThread::currentThreadId();
    QElapsedTimer time;
    time.start();
    int temp;
    for (int i = 0; i < m_list.size(); ++i)
    {
        for (int j = 0; j < m_list.size()-i-1; ++j)
        {
            if (m_list[j] > m_list[j + 1])
            {
                temp = m_list[j];
                m_list[j] = m_list[j + 1];
                m_list[j + 1] = temp;
            }
        }
    }
    int milsec = time.elapsed();
    qDebug() << "shared" << milsec << "second";
    emit finish(m_list);
}
  • 主線程.h
#include <QtWidgets/QWidget>
#include"ui_list.h"

class list : public QWidget
{
    Q_OBJECT

public:
    list(QWidget *parent = Q_NULLPTR);
signals:
    void stating(int num);//設置要生成隨機數個數

private:
    Ui::listClass ui;
};
  • 主線程.cpp
#include "list.h"
#include"myThread.h"
list::list(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
    //1.創建子線程對象
    myThread* thread = new myThread();
    BubbleSort* bub_thread = new BubbleSort();
    //向子線程發送要生成的隨機數個數
    connect(this,&list::stating, thread, &myThread::scvnum);
    //2啟動子線程
    connect(ui.pushButton, &QPushButton::clicked,  [=]() {
        emit stating(1000);//主線程設置子線程隨機數的個數
        thread->start();
    
        
        });
    connect(thread, &myThread::sendArray, bub_thread, &BubbleSort::rcvArray);

    //3接收子線程發送的數據
    connect(thread, &myThread::sendArray,  [=](QVector<int>list) {
        for (int i = 0; i < list.size(); ++i)
        {
            ui.randlist->addItem(QString::number(list.at(i)));
        }
        });
    connect(thread, &myThread::sendArray, [=](QVector<int>list){
        bub_thread->start();
        });
    connect(bub_thread, &BubbleSort::finish, [=](QVector<int>list) {
        for (int i = 0; i < list. size(); ++i)
        {
            ui.bubblelist->addItem(QString::number(list.at(i)));
        }
        });
}

實現效果:

方法二:moveToThread() 

思路:

  • 新建myThread類,用於生成隨機數(working函數),在接受到主線程的信號后開始生成隨機數
  • 新建BubbleSort類,用於排序(working函數),在接受到myThread類生成的隨機數后開始排序
  • 最后顯示在界面

代碼實現:

  • 子線程.h
#pragma once
#include"qthread.h"
#include"qvector.h"
#include"qobject.h"
//新建隨機數類
class myThread :
    public QObject
{
    Q_OBJECT
public:
    myThread(QObject* parent = nullptr);
    void working(int num);//生成隨機數
signals:
    void sendArray(QVector<int>num);//發送
private:
    int m_num;
};

//新建冒泡排序類
class BubbleSort :
    public QObject
{
    Q_OBJECT
public:
    BubbleSort(QObject* parent = nullptr);
    void working(QVector<int>list);//要接收的是排序的數

signals:
    void finish(QVector<int>list);//排序完成后發送一個finish信號

};
  • 子線程.cpp
#include "myThread.h"
#include"qelapsedtimer.h"
#include"qdebug.h"

myThread::myThread(QObject* parent) :QObject(parent)
{

}
BubbleSort::BubbleSort(QObject* parent) : QObject(parent)
{

}


void myThread::working(int num)
{
    qDebug() << "child thread id" << QThread::currentThreadId();
    QVector<int> list;
    QElapsedTimer time;
    time.start();
    for (int i = 0; i < num; i++)
    {
        list.push_back(qrand() % 100000);
    }
    int milsec = time.elapsed();
    qDebug() << "The number of" << m_num << "generated";
    qDebug() << "shared" << milsec << "second";
    emit sendArray(list);
}


void BubbleSort::working(QVector<int>list)
{
    qDebug() << "BubbleSort thread id:" << QThread::currentThreadId();
    QElapsedTimer time;
    time.start();
    int temp;
    for (int i = 0; i < list.size(); ++i)
    {
        for (int j = 0; j < list.size()-i-1; ++j)
        {
            if (list[j] > list[j + 1])
            {
                temp = list[j];
                list[j] = list[j + 1];
                list[j + 1] = temp;
            }
        }
    }
    int milsec = time.elapsed();
    qDebug() << "shared" << milsec << "second";
    emit finish(list);
}
  • 主線程.h
#include <QtWidgets/QWidget>
#include"ui_list.h"

class list : public QWidget
{
    Q_OBJECT

public:
    list(QWidget *parent = Q_NULLPTR);
signals:
    void stating(int num);//設置要生成隨機數個數

private:
    Ui::listClass ui;
};
  • 主線程.cpp
#include "list.h"
#include"myThread.h"
list::list(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
    //1.創建QThread對象
    QThread* thread1 = new QThread;
    QThread* thread2 = new QThread;
    //2.創建子線程類對象
    myThread* myth = new myThread();
    BubbleSort* bub = new BubbleSort();
    //3通過movetothread將子線程對象移動到QThread對象中
    myth->moveToThread(thread1);
    bub->moveToThread(thread2);
    //4啟動子線程

    //先向子線程發送要生成的隨機數個數
    connect(this,&list::stating, myth, &myThread::working);
    //再啟動子線程
    connect(ui.pushButton, &QPushButton::clicked,  [=]() {
        emit stating(1000);//主線程設置子線程隨機數的個數
        thread1->start();        
        });
    //將生成好的隨機數發送給BubbleSort類
    connect(myth, &myThread::sendArray, bub, &BubbleSort::working);

    //將生成好的隨機數顯示在界面
    connect(myth, &myThread::sendArray,  [=](QVector<int>list) {
        for (int i = 0; i < list.size(); ++i)
        {
            ui.randlist->addItem(QString::number(list.at(i)));
        }
        });
    //發送的同時啟動排序算法
    connect(myth, &myThread::sendArray, [=](QVector<int>list){
        thread2->start();
        });
    //將排序好的數顯示在界面
    connect(bub, &BubbleSort::finish, [=](QVector<int>list) {
        for (int i = 0; i < list. size(); ++i)
        {
            ui.bubblelist->addItem(QString::number(list.at(i)));
        }
        });
}

結論

通過對比,我們可以發現:

  • 由於第二種方法,我們可以自定義帶參的子線程運行函數,因此代碼更加簡潔。
  • 在第二種方法中,我們還可以隨意修改需要在哪個線程中運行,代碼也更加靈活。
  • 第一種方法適合在線程中處理單一事件,其邏輯簡單(只需要新建一個繼承自QThread類的對象,重寫run函數,然后啟動即可),對於需要在一個線程中處理多個事件,還是用第二種方法比較好。
  • 為什么不能在第二種方法中,給定義的子線程對象添加父類呢? :由於添加了父類以后就不能再移動到QThread中去了

如何進行線程資源的釋放?

  1. 在new對象時候,直接用this指定其父類(即放入對象數中)
  2. 在程序最后自行釋放資源
    connect(this, &list::destroyed, this, [=]() {
        thread1->quit();
        thread1->wait();
        thread1->deleteLater();
        thread2->quit();
        thread2->wait();
        thread2->deleteLater();
        myth->deleteLater();
        bub->deleteLater();
        });


免責聲明!

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



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