1. 程式人生 > >QThread與多執行緒(比較清楚)

QThread與多執行緒(比較清楚)

QThread類為我們提供了一種平臺無關的管理執行緒的方式。一個QThread物件管理應用程式中的一個執行緒,該執行緒從run()函式開始執行。並且,預設情況下,我們可以在run()函式中通過呼叫QThread::exec()函式來在當前執行緒中開啟一個事件迴圈。

而使用QThread開啟執行緒的最常用的方式 就是繼承QThread類,重寫其run()方法,因為我們剛才就說過,QThread代表的執行緒就是從run()函式開始執行的。

例如:

class WorkerThread : public QThread
{
Q_OBJECT
void run() Q_DECL_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();
}
在這個例子中,該執行緒會在run()函式返回後退出。又因為我們沒有在run()函式中呼叫exec(),所以該執行緒中沒有執行事件迴圈。
另一種使用執行緒的方法,是將要完成的工作封裝到一個工作者物件中,然後使用QObject::moveToThread()函式將該物件移動到一個執行緒物件中。如下:

class Worker : public QObject
{
Q_OBJECT

public slots:
void doWork(const QString ¶meter) {
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 &);
};
在這個例子中,Worker的槽函式會在一個獨立的執行緒中執行。但是,我們可以自由的將Worker的槽函式連線到任意的訊號上,任意的物件上,任意的執行緒中。並且,由於Qt提供的訊號和槽連線型別中的queued connections型別,使我們可以安全的跨執行緒連線訊號和槽。
並且,很重要的一點是,QThread物件是存活在建立它的那個執行緒中,而不是在執行run()函式的新執行緒中。這意味著,連線到QThread的所有的排隊型槽函式都是在舊執行緒中執行的。因此,如果我們希望我們呼叫的槽函式也在新執行緒中執行,就必須使用這種工作物件的方式。新的槽函式 不應該直接實現在QThread的子類中。

還有,當子類化QThread時,要記住的一點是執行緒物件的建構函式在舊執行緒中執行,而run()在新執行緒中執行。所以,如果在這兩個函式中都訪問了一個成員變數,那麼就是在兩個不同的執行緒中訪問的,要確保訪問的安全性。

下面,我們分別使用這兩種方式,通過列印執行緒id的方法,來看一下它們的區別。
我們先寫一個工作者類,繼承自QObject:

#ifndef WORKER_H
#define WORKER_H

#include <QObject>
#include <QThread>
#include <QDebug>

class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = 0);

public slots:
void start();
};

#endif // WORKER_H

#include "worker.h"

Worker::Worker(QObject *parent) : QObject(parent)
{

}

void Worker::start()
{
qDebug() << "child thread: " << QThread::currentThreadId();
}
我在該類中,只定義了一個start()函式,作為我們執行緒的執行體,其所做的工作也很簡單,只是打印出本執行緒的id。
接下來,再寫一個Controller類,其也繼承自QObject,用來控制執行緒的啟動。

#ifndef CONTROLLER_H
#define CONTROLLER_H

#include <QObject>
#include "worker.h"

class Controller : public QObject
{
Q_OBJECT
public:
explicit Controller(QObject *parent = 0);
void start();

signals:
void operate();

private:
QThread workerThread;
};

#endif // CONTROLLER_H

#include "controller.h"

Controller::Controller(QObject *parent) : QObject(parent)
{
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::start);
workerThread.start();
}

void Controller::start()
{
emit operate();
}
在這個類中,我們在聲明瞭一個start()函式和一個operate()訊號,還有一個QThread物件。然後在建構函式中,例項化一個Worker類物件,再使用moveToThread()函式,將其移動到我們定義的執行緒物件中;最後,為了啟動我們的執行體,我們將operate()訊號連線到Worker的start()槽函式上。然後,啟動我們的執行緒物件。
main函式如下:

#include <QCoreApplication>
#include "controller.h"

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

qDebug() << "main thread: " << QThread::currentThreadId();

Controller controller;
controller.start();

return a.exec();
}
我們定義一個Controller的物件,然後呼叫其start()方法即可。而根據我們在Controller::start()的實現中,發出了operate()訊號,該訊號又連線到Worker::start(),所以會觸發該函式的執行,即我們的執行緒執行體。
其執行結果如下:

 

可以看出,子執行緒的槽函式確實在一個獨立的執行緒中執行。

而如果我們用繼承QThread的方法來實現該功能,則會看到不同的結果。

先實現Thread類如下:

#ifndef THREAD_H
#define THREAD_H
#include <QThread>
#include <QDebug>

class Thread : public QThread
{
Q_OBJECT
public:
Thread(QObject *parent = Q_NULLPTR);

public slots:
void Come();

protected:
void run() Q_DECL_OVERRIDE;
};

#endif // THREAD_H

#include "thread.h"

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

}

void Thread::Come()
{
qDebug() << "child thread: " << QThread::currentThreadId();
}

void Thread::run()
{
exec();
}
我們繼承了QThread類,重新了run()方法,這是繼承QThread開啟執行緒所必須的一步。另外,我們還定義了一個槽函式,Come(),在該函式中列印了本執行緒的id。為了讓執行緒不退出,我們在run()函式中呼叫了QThread::exec()函式,為該執行緒開啟一個事件迴圈,等待某個訊號來觸發Come()槽函式。
接下來,再修改Controller類如下:

#ifndef CONTROLLER_H
#define CONTROLLER_H

#include <QObject>
#include "worker.h"

class Controller : public QObject
{
Q_OBJECT
public:
explicit Controller(QObject *parent = 0);
void start();

signals:
void operate();
};

#endif // CONTROLLER_H

#include "controller.h"
#include "thread.h"

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

void Controller::start()
{
emit operate();
}
我們只在start()函式中,傳送了operate()訊號。
再修改main函式如下:

#include <QCoreApplication>
#include "controller.h"
#include "thread.h"

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

qDebug() << "main thread: " << QThread::currentThreadId();

Controller controller;
Thread thread;
QObject::connect(&controller, SIGNAL(operate()), &thread, SLOT(Come()));
thread.start();
controller.start();

return a.exec();
}

我們先定義了Controller和Thread物件,然後將Controller的operate()訊號連線到Thread的Come()槽函式上,緊接著啟動執行緒,等待訊號。最後呼叫Controller的start()方法,傳送我們定義的訊號,該訊號又會觸發Thread類的槽函式,打印出執行緒ID。
該種方式的執行結果如下:

 

可見,執行緒類Thread中的槽函式並沒有執行在run()函式所在的新執行緒中,而是和main函式在同一個執行緒中,即建立執行緒物件的執行緒。這有時恐怕不是我們想要的。所以,當需要執行緒中的槽函式完全在另一個新執行緒中執行時,就需要使用moveToThread()的方法。

 

---------------------
作者:求道玉
來源:CSDN
原文:https://blog.csdn.net/Amnes1a/article/details/70171519
版權宣告:本文為博主原創文章,轉載請附上博文連結!