引言
前面幾篇已經對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函數)
首先,以文字形式來說明需要哪幾個步驟。
- 自定義一個自己的類,使其繼承自QThread類;
- 在自定義類中覆寫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
- 自定義的類不能指定父對象
thread1=new workThread1();//初始化
- QThread和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):當控制權回到接受者所在線程的事件循環式,槽函數被調用。槽函數在接收者所在線程執行。
因此我們需要注意的是:
- 主線程對象發出信號連接QThread子類的槽函數,QThread子類對象在主線程創建的,無論采用哪種連接方式,槽函數都屬在主線程調用。(如果在重寫的run函數中調用了槽函數,此時槽函數在次線程執行,注意數據安全)。
- 次線程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中去了
如何進行線程資源的釋放?
- 在new對象時候,直接用this指定其父類(即放入對象數中)
- 在程序最后自行釋放資源
connect(this, &list::destroyed, this, [=]() { thread1->quit(); thread1->wait(); thread1->deleteLater(); thread2->quit(); thread2->wait(); thread2->deleteLater(); myth->deleteLater(); bub->deleteLater(); });