目錄
- 方法1.繼承QThread
- 使用案例
- 總結
- 方法2.將qobject對象moveToThread(官方推薦)
- 使用案例
- 總結
- 方法3.QRunnable + QThreadPool
- 使用案例
- 總結
- 方法4.快速線程QtConcurrent+QFutureWatcher
- 使用案例
- 總結
- 代碼下載
方法1.繼承QThread
需要實現QThread的抽象函數run
#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H#include <QObject>
#include <QThread>
#include <QDebug>class WorkerThread : public QThread
{Q_OBJECT
public:explicit WorkerThread(QObject *parent = nullptr);void func1();protected:virtual void run();signals:
};#endif // WORKERTHREAD_H#include "workerthread.h"WorkerThread::WorkerThread(QObject *parent): QThread{parent}
{}void WorkerThread::func1()
{qDebug()<< __FUNCTION__<< QThread::currentThread();
}void WorkerThread::run()
{qDebug()<< __FUNCTION__<< QThread::currentThread();
}
使用案例
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);qDebug()<< "主線程:" << QThread::currentThread();wt = new WorkerThread(this);wt->start();//執行WorkerThread的run在子線程中wt->func1(); //主線程connect(ui->pushButton,&QPushButton::clicked,wt,&WorkerThread::func1); //主線程}Widget::~Widget()
{delete ui;
}
總結
run函數是在子線程中執行的
其成員函數func1
a)直接調用的方式是在創建對象的線程(主線程)中執行的
b)信號槽連接的方式也是在創建對象的線程(主線程)中執行的
直接調用接口,如果包含了run線程中使用的變量可能就會導致數據競爭
此時就得考慮加鎖
如果run線程中獲取到了鎖,主線程調用WorkerThread的接口會導致阻塞,界面卡頓=
使用場景,不需要進行線程間數據交互
方法2.將qobject對象moveToThread(官方推薦)
#ifndef WORKER_H
#define WORKER_H#include <QObject>class Worker : public QObject
{Q_OBJECT
public:explicit Worker(QObject *parent = nullptr);~Worker();void func1();signals:
};#endif // WORKER_H
#include "worker.h"
#include <QThread>
#include <QDebug>Worker::Worker(QObject *parent): QObject{parent}
{}Worker::~Worker()
{
qDebug()<< __FUNCTION__ << QThread::currentThread();
}void Worker::func1()
{qDebug()<< __FUNCTION__ << QThread::currentThread();
}
使用案例
#ifndef FORM_H
#define FORM_H#include <QWidget>
#include <QThread>
#include <QDebug>#include "worker.h"namespace Ui {
class Form;
}class Form : public QWidget
{Q_OBJECTpublic:explicit Form(QWidget *parent = nullptr);~Form();
signals:void closeThread();private:Ui::Form *ui;
};#endif // FORM_H
#include "form.h"
#include "ui_form.h"Form::Form(QWidget *parent): QWidget(parent), ui(new Ui::Form)
{ui->setupUi(this);//根據親和性,不應設置為其他線程的成員對象QThread *th = new QThread();Worker *worker = new Worker();qDebug()<< "主線程:" << QThread::currentThread();worker->moveToThread(th);connect(th,&QThread::finished,worker,&Worker::deleteLater);connect(th,&QThread::finished,worker,&QThread::deleteLater);th->start(); //啟動子線程worker->func1(); //錯誤行為connect(ui->pushButton,&QPushButton::clicked,worker,&Worker::func1); //正確線程間通信connect(this,&Form::closeThread,th,&QThread::quit); //關閉線程
}Form::~Form()
{delete ui;emit closeThread();}
總結
使用場景: 線程之間頻繁交互的時候(官方推薦)
①禁止行為:禁止直接調用子線程對象接口或者修改數據
因為這會導致跨線程訪問問題,可能引發數據競爭、死鎖或程序崩潰。
應該通過信號槽連接進行線程間通信。
②獨立的事件循環
子線程開啟會產生一個獨立的事件循環,此時異步線程信號請求會在此次統一處理
不會對發起請求的線程阻塞;
③Qt 的父子對象與線程規則?
?規則 1?:所有 QObject 父子對象必須位于同一線程。
?規則 2?:調用 moveToThread() 會改變對象的線程親和性(Thread Affinity)。
?沖突點?:如果將一個已移動到子線程的 QObject 設置為另一個線程中對象的子對象,Qt 會觸發斷言崩潰(如 QObject: Cannot create children for a parent that is in a different thread)。
簡單點說既是;A線程中的對象不應該成為B線程的父對象或者子對象
方法3.QRunnable + QThreadPool
#ifndef WORKERTASK_H
#define WORKERTASK_H#include <QRunnable>class WorkerTask : public QRunnable
{
public:WorkerTask();~WorkerTask();
protected:virtual void run();
};#endif // WORKERTASK_H
#include "workertask.h"
#include <QThread>
#include <QDebug>WorkerTask::WorkerTask() {setAutoDelete(true);
}WorkerTask::~WorkerTask()
{
qDebug()<< __FUNCTION__<< QThread::currentThread();
}void WorkerTask::run()
{
qDebug()<< __FUNCTION__<< QThread::currentThread();
}
使用案例
#ifndef FORM1_H
#define FORM1_H#include <QWidget>
#include <QThread>
#include <QDebug>namespace Ui {
class Form1;
}class Form1 : public QWidget
{Q_OBJECTpublic:explicit Form1(QWidget *parent = nullptr);~Form1();private:Ui::Form1 *ui;
};#endif // FORM1_H
#include "form1.h"
#include "ui_form1.h"
#include "workertask.h"
#include <QThreadPool>Form1::Form1(QWidget *parent): QWidget(parent), ui(new Ui::Form1)
{ui->setupUi(this);qDebug()<< "主線程:" << QThread::currentThread();QThreadPool::globalInstance()->start(new WorkerTask());}Form1::~Form1()
{delete ui;
}
總結
場景:適合短生命周期的任務
用完就釋放
方法4.快速線程QtConcurrent+QFutureWatcher
其他線程都得創建一堆的對象,管理對象的創建與釋放生命周期,
但是QtConcurrent不用,使用QtConcurrent我們可以快速開啟一個線程
使用案例
#include "form2.h"
#include "ui_form2.h"
#include <QDebug>
#include <QThread>
#include <QtConcurrent>
#include <QtConcurrent/QtConcurrentMap>Form2::Form2(QWidget *parent): QWidget(parent), ui(new Ui::Form2)
{ui->setupUi(this);qDebug()<< "主線程:" << QThread::currentThread();
}Form2::~Form2()
{delete ui;
}void Form2::on_pushButton_clicked()
{QList<int> list = {1, 2, 3, 4, 5};//通過map開啟多個線程執行, 如果使用for每個元素處理是耗時的,但是采用此法可以并行處理// QFuture<void> future = QtConcurrent::map(list, [](int &value) {// value *= 2; // 將每個元素乘以2// qDebug()<< __FUNCTION__ << QThread::currentThread();// });QFuture<int> future = QtConcurrent::run([]() -> int {// 計算并返回結果return 42;});qDebug()<< future.result(); //阻塞等待結構
}
綁定監聽器,全部執行完則發出fnish信號
QFutureWatcher<void> watcher;
connect(&watcher, &QFutureWatcher<void>::finished, this, []() {qDebug() << "圖像處理完成";
});watcher.setFuture(future);
總結
?避免修改共享數據?:盡量使用值傳遞而非引用傳遞
?使用輕量級函數?:并行執行的函數應該盡可能輕量
?合理設置線程池大小?:使用 QThreadPool::globalInstance()->setMaxThreadCount()
?處理異常?:確保并行函數不會拋出未捕獲的異常
代碼下載
鏈接: 下載