QT 中4種多線程實現方法


一、QThread類的run
一、實現方法:

新建一個集成QThread的類,重寫虛函數run,通過run啟動線程

二、示例:

class WorkerThread : public QThread
{
Q_OBJECT
void run() override {
QString result;
/* ... here is the expensive or blocking operation ... */
emit resultReady(result);
}
signals:
void resultReady(const QString &s);
};

void MyObject::startWorkInAThread()
{
WorkerThread *workerThread = new WorkerThread(this);
connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
}
三、特點:

1、優點:可以通過信號槽與外界進行通信。
2、缺點:1每次新建一個線程都需要繼承QThread,實現一個新類,使用不太方便。
要自己進行資源管理,線程釋放和刪除。並且頻繁的創建和釋放會帶來比較大的內存開銷。
3、適用場景:QThread適用於那些常駐內存的任務。

二、QThread類的moveToThread
一、實現方法:

創建一個繼承QObject的類(myobject),然后new一個Qthread,並把創建的myobject類movetothread到創建好的子線程中,然后start子線程,這樣就實現了一個子線程。主線程通過發送信號,調用myobject中的方法,從而實現在子線程中的計算。

二、示例:


class Worker : public QObject
{
Q_OBJECT

public slots:
void doWork(const QString &parameter) {
QString result;
/* ... here is the expensive or blocking operation ... */
emit resultReady(result);
}

signals:
void resultReady(const QString &result);
};

class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::doWork);
connect(worker, &Worker::resultReady, this, &Controller::handleResults);
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
signals:
void operate(const QString &);
};
三、特點:

MovetoThreadTest.cpp 中用到了窗體中的幾個按鈕,在如用代碼是首相創建一個窗體,在窗體添加按鈕,然后跟據按鈕的名字進行連接即可;

主線程如果要在子線程中運行計算必須通過發信號的方式調用,或者通過控件的信號;需要說明在創建連接時如果第五的參數為DirectConnection時,調用的槽函數是在主線程中運行;如果想通過子線程向主線程調用方法,也必須通過發信號的方式觸發主線程的函數。

Qt::AutoConnection,t::DirectConnection,t::QueuedConnection,t::BlockingQueuedConnection,t::UniqueConnection

Qt::AutoCompatConnection這里面一共有六種方式。

前兩種比較相似,都是同一線程之間連接的方式,不同的是Qt::AutoConnection是系統默認的連接方式。這種方式連接的時候,槽不是馬上被執行的,而是進入一個消息隊列,待到何時執行就不是我們可以知道的了,當信號和槽不是同個線程,會使用第三種QT::QueueConnection的鏈接方式。如果信號和槽是同個線程,調用第二種Qt::DirectConnection鏈接方式。
第二種Qt::DirectConnection是直接連接,也就是只要信號發出直接就到槽去執行,無論槽函數所屬對象在哪個線程,槽函數都在發射信號的線程內執行,一旦使用這種連接,槽將會不在線程執行!。
第三種Qt::QueuedConnection和第四種Qt::BlockingQueuedConnection是相似的,都是可以在不同進程之間進行連接的,不同的是,這里第三種是在對象的當前線程中執行,並且是按照隊列順序執行。當當前線程停止,就會等待下一次啟動線程時再按隊列順序執行 ,等待QApplication::exec()或者線程的QThread::exec()才執行相應的槽,就是說:當控制權回到接受者所依附線程的事件循環時,槽函數被調用,而且槽函數在接收者所依附線程執行,使用這種連接,槽會在線程執行。
第四種Qt::BlockingQueuedConnection是(必須信號和曹在不同線程中,否則直接產生死鎖)這個是完全同步隊列只有槽線程執行完才會返回,否則發送線程也會等待,相當於是不同的線程可以同步起來執行。
第五種Qt::UniqueConnection跟默認工作方式相同,只是不能重復連接相同的信號和槽;因為如果重復鏈接就會導致一個信號發出,對應槽函數就會執行多次。
第六種Qt::AutoCompatConnection是為了連接QT4 到QT3的信號槽機制兼容方式,工作方式跟Qt::AutoConnection一樣。顯然這里我們應該選擇第三種方式,我們不希望子線程沒結束主線程還要等,我們只是希望利用這個空閑時間去干別的事情,當子線程執行完了,只要發消息給主線程就行了,到時候主線程會去響應。

moveToThread對比傳統子類化Qthread更靈活,僅需要把你想要執行的代碼放到槽,movetothread這個object到線程,然后拿一個信號連接到這個槽就可以讓這個槽函數在線程里執行。可以說,movetothread給我們編寫代碼提供了新的思路,當然不是說子類化qthread不好,只是你應該知道還有這種方式去調用線程。

輕量級的函數可以用movethread,多個短小精悍能返回快速的線程函數適用 ,無需創建獨立線程類,例如你有20個小函數要在線程內做, 全部扔給一個QThread。而我覺得movetothread和子類化QThread的區別不大,更可能是使用習慣引導。又或者你一開始沒使用線程,但是后邊發覺這些代碼還是放線程比較好,如果用子類化QThread的方法重新設計代碼,將會有可能讓你把這一段推到重來,這個時候,moveThread的好處就來了,你可以把這段代碼的從屬着movetothread,把代碼移到槽函數,用信號觸發它就行了。其它的話movetothread它的效果和子類化QThread的效果是一樣的,槽就相當於你的run()函數,你往run()里塞什么代碼,就可以往槽里塞什么代碼,子類化QThread的線程只可以有一個入口就是run(),而movetothread就有很多觸發的入口。

 

三、QRunnalble的run
Qrunnable是所有可執行對象的基類。我們可以繼承Qrunnable,並重寫虛函數void QRunnable::run () 。我們可以用QThreadPool讓我們的一個QRunnable對象在另外的線程中運行,如果autoDelete()返回true(默認),那么QThreadPool將會在run()運行結束后自動刪除Qrunnable對象。可以調用void QRunnable::setAutoDelete ( bool autoDelete )更改auto-deletion標記。需要注意的是,必須在調用QThreadPool::start()之前設置,在調用QThreadPool::start()之后設置的結果是未定義的。

一、實現方法:

1、繼承QRunnable。和QThread使用一樣, 首先需要將你的線程類繼承於QRunnable。

2、重寫run函數。還是和QThread一樣,需要重寫run函數,run是一個純虛函數,必須重寫。

3、使用QThreadPool啟動線程

二、示例:

class Runnable:public QRunnable
{
//Q_OBJECT 注意了,Qrunnable不是QObject的子類。
public:
Runnable();
~Runnable();
void run();
};


Runnable::Runnable():QRunnable()
{

}

Runnable::~Runnable()
{
cout<<"~Runnable()"<<endl;
}

void Runnable::run()
{
cout<<"Runnable::run()thread :"<<QThread::currentThreadId()<<endl;
cout<<"dosomething ...."<<endl;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
cout<<"mainthread :"<<QThread::currentThreadId()<<endl;

 Runable runObj;
   // QThreadPool::globalInstance()->start(&runObj);
    runObj.setAutoDelete(true);
    QThreadPool *threadPool =QThreadPool::globalInstance();
    threadPool->start(&runObj);
    qDebug() <<"data from GUI thread " << QThread::currentThread();
    threadPool->waitForDone();
 
        


returna.exec();
}


三、特點:

優點:無需手動釋放資源,QThreadPool啟動線程執行完成后會自動釋放。
缺點:不能使用信號槽與外界通信。
適用場景:QRunnable適用於線程任務量比較大,需要頻繁創建線程。QRunnable能有效減少內存開銷。

四、QtConcurrent的run
Concurrent是並發的意思,QtConcurrent是一個命名空間,提供了一些高級的 API,使得在編寫多線程的時候,無需使用低級線程原語,如讀寫鎖,等待條件或信號。使用QtConcurrent編寫的程序會根據可用的處理器內核數自動調整使用的線程數。這意味着今后編寫的應用程序將在未來部署在多核系統上時繼續擴展。

QtConcurrent::run能夠方便快捷的將任務丟到子線程中去執行,無需繼承任何類,也不需要重寫函數,使用非常簡單。詳見前面的文章介紹,這里不再贅述。

需要注意的是,由於該線程取自全局線程池QThreadPool,函數不能立馬執行,需要等待線程可用時才會運行。

一、實現方法:

1  QtConcurrent::run()

  QtConcurrent 是命名空間 (namespace),它提供了高層次的函數接口 (APIs),使所寫程序,可根據計算機的 CPU 核數,自動調整運行的線程數目。

  下面是 Qt 例程 runfunction,對應目錄為  D:\Qt\Qt5.12.3\Examples\Qt-5.12.3\qtconcurrent\runfucntion

1.1  .pro 工程文件

  使用 QtConcurrent 模塊,需要在 .pro 中添加: QT += concurrent 

1
2
3
4
QT += concurrent widgets
CONFIG += console
 
SOURCES += main.cpp   

1.2  main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <QString>
#include <QDebug>
#include <QThread>
#include <QApplication>
 
#include "qtconcurrentrun.h"
 
using  namespace  QtConcurrent;
 
void  func(QString name)
{
     qDebug() << name <<  "from"  << QThread::currentThread();
}
 
int  main( int  argc,  char  **argv)
{
     QApplication app(argc, argv);
 
     QFuture< void > fut1 = run(func, QString( "Thread 1" ));
     QFuture< void > fut2 = run(func, QString( "Thread 2" ));
 
     fut1.waitForFinished();
     fut2.waitForFinished();
}  

  用 QtConcurrent::run() 函數,分別將  func() 運行在兩個不同的線程中,輸出結果如下: 

1
2
"Thread 1"  from QThread(0x1b74fd2ebc0, name =  "Thread (pooled)" )
"Thread 2"  from QThread(0x1b74fd534e0, name =  "Thread (pooled)"

  下面是 QtConcurrent::run() 的詳細使用,閱讀完 2 和 3,再看上面的 runfunction 例程,就容易理解了。

 

2  普通函數

2.1  將函數運行在某一個線程中 

1
2
extern  void  func();
QFuture< void > future = QtConcurrent::run(func);   

  如果要為其指定線程池,可以將線程池的指針作為第一個參數傳遞進去

1
2
3
extern  void  func();
QThreadPool pool;
QFuture< void > future = QtConcurrent::run(&pool, func);  

2.2  向該函數傳遞參數

  需要傳遞的參數,則跟在函數名之后,依次加入 

1
2
3
4
5
6
extern  void  FuncWithArguments( int  arg1,  const  QString &string);
 
  int  integer = ...;
  QString string = ...;
 
  QFuture< void > future = QtConcurrent::run(FuncWithArguments,integer,string);   

2.3  獲取該函數的計算結果 

1
2
3
4
5
6
7
extern  QString Func( const  QByteArray &input);
 
QByteArray byte_array = ...;
 
QFuture<QString> future = QtConcurrent::run(func, byte_array);
...
QString result = future.result(); 

 

3  成員函數

  將類中的成員函數運行在某一個線程中,可將指向該類實例的 引用或指針 作為 QtConcurrent::run 的第一個參數傳遞進去,

  常量成員函數一般傳遞 常量引用 (const reference),而非常量成員函數一般傳遞 指針 (pointer)

3.1  常量成員函數

   在一個單獨的線程中,調用 QByteArray 的常量成員函數 split(),傳遞給 run() 函數的參數是 bytearray 

1
2
3
4
QByteArray bytearray =  "hello world" ;
QFuture<QList<QByteArray> > future = QtConcurrent::run(bytearray, &QByteArray::split,  ',' );
...
QList<QByteArray> result = future.result();   

3.2  非常量成員函數

   在一個單獨的線程中,調用 QImage 的非常量成員函數 invertPixels(),傳遞給 run() 函數的參數是 &image 

1
2
3
4
5
QImage image = ...;
QFuture< void > future = QtConcurrent::run(&image, &QImage::invertPixels, QImage::InvertRgba);
...
future.waitForFinished();
// At this point, the pixels in 'image' have been inverted  

 

三、特點:

//調用外部函數 QFuture<void> f1 =QtConcurrent::run(func,QString("aaa"));

//調用類成員函數 QFuture<void> f2 =QtConcurrent::run(this,&MainWindow::myFunc,QString("bbb"));

要為其指定線程池,可以將線程池的指針作為第一個參數傳遞進去

向該函數傳遞參數,需要傳遞的參數,則跟在函數名之后

可以用run函數的返回值funIr來控制線程。
如: funIr.waitForFinished(); 等待線程結束,實現阻塞。
funIr.isFinished() 判斷線程是否結束
funIr, isRunning() 判斷線程是否在運行
funIr的類型必須和線程函數的返回值類型相同,可以通過
funIr.result() 取出線程函數的返回值

缺點,不能直接用信號和槽函數來操作線程函數,eg : 當線程函數結束時,不會觸發任何信號。

原文鏈接:https://blog.csdn.net/weixin_44840658/article/details/108625659

原文鏈接:https://www.cnblogs.com/xinxue/p/6840315.html


免責聲明!

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



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