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

QThread與多線程(比較清楚)

endif qstring 變量 結果 nec fine 控制 null 情況下

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
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

QThread與多線程(比較清楚)