QThread詳解


回顧Qt之線程(QThread),里面講解了如何使用線程,但還有很多人留言沒有看明白,那么今天我們來一起瞅瞅關於QThread管理線程的那些事兒。。。


一、線程管理

1、線程啟動

void start(Priority priority = InheritPriority)

調用后會執行run()函數,但在run()函數執行前會發射信號started(),操作系統將根據優先級參數調度線程。如果線程已經在運行,那么這個函數什么也不做。優先級參數的效果取決於操作系統的調度策略。特別是那些不支持線程優先級的系統優先級將會被忽略(例如在Linux中,更多細節請參考http://linux.die.net/man/2/sched_setscheduler)。

 

2、線程執行

int exec()

進入事件循環並等待直到調用exit(),返回值是通過調用exit()來獲得,如果調用成功則范圍0。

virtual void run();

線程的起點,在調用start()之后,新創建的線程就會調用這個函數,默認實現調用exec(),大多數需要重新實現這個功能,便於管理自己的線程。該方法返回時,該線程的執行將結束。

 

3、線程退出

void quit()

告訴線程事件循環退出,返回0表示成功,相當於調用了QThread::exit(0)。

void exit(int returnCode = 0)

告訴線程事件循環退出。

調用這個函數后,線程離開事件循環后返回,QEventLoop::exec()返回returnCode,

按照慣例0表示成功,任何非0值表示失敗。

void terminate()

終止線程,線程可能會立即被終止也可能不會,這取決於操作系統的調度策略,使用terminate()之后再使用QThread::wait()確保萬無一失。

當線程被終止后,所有等待中的線程將會被喚醒。

警告:此功能比較危險,不鼓勵使用。線程可以在代碼執行的任何點被終止。線程可能在更新數據時被終止,從而沒有機會來清理自己,解鎖等等。。。總之,只有在絕對必要時使用此功能。

建議:一般情況下,都在run函數里面設置一個標識符,可以控制循環停止。然后才調用quit函數,退出線程。

 

4、線程等待

void msleep(unsigned long msecs)

強制當前線程睡眠msecs毫秒

void sleep(unsigned long secs)

強制當前線程睡眠secs

void usleep(unsigned long usecs)

強制當前線程睡眠usecs微秒

bool wait(unsigned long time = ULONG_MAX);

線程將會被阻塞,等待time毫秒。和sleep不同的是,如果線程退出,wait會返回。

 

5、線程狀態

    bool isFinished() const    

    線程是否結束

    bool  isRunning() const    
    線程是否正在運行
 

6、線程優先級

    void setPriority (Priority priority)
    這個函數設置正在運行線程的優先級。如果線程沒有運行,此功能不執行任何操作並立即返回。使用的start()來啟動一個線程具有特定的優先級。
    優先級參數可以是QThread::Priority枚舉除InheritPriortyd的任何值。
    Priority priority () const
     下面來看下優先級中的各個枚舉值:

Constant

Value

Description

QThread::IdlePriority

0

沒有其它線程運行時才調度.

QThread::LowestPriority

1

比LowPriority調度頻率低.

QThread::LowPriority

2

比NormalPriority調度頻率低.

QThread::NormalPriority

3

操作系統默認的默認優先級.

QThread::HighPriority

4

比NormalPriority調度頻繁.

QThread::HighestPriority

5

比HighPriority調度頻繁.

QThread::TimeCriticalPriority

6

盡可能頻繁的調度.

QThread::InheritPriority

7

使用和創建線程同樣的優先級. 這是默認值.

 
 
二、主線程、次線程

Qt之線程(QThread)一節中我介紹了QThread 的兩種使用方法:

1、子類化 QThread(不使用事件循環)

這是官方手冊、例子以及相關書籍中都介紹的一種常用的方法。

a. 子類化 QThread

b. 重載 run 函數,run函數內有一個while或for的死循環(模擬耗時操作)

c. 設置一個標記為來控制死循環的退出。

 

2、子類化 QObject

a. 子類化 QObject

b定義槽函數

c. 將該子類的對象moveToThread到新線程中

 

run 對於線程的作用相當於main函數對於應用程序。它是線程的入口,run的開始和結束意味着線程的開始和結束。

采用這兩種做法,毫無疑問都會在次線程中運行(這里說的是,run中的邏輯以及子類化QObject后連接通過moveToThread然后連接到QThread的started()信號的槽函數,這個下面會詳細講解)。

那么,線程中的槽函數是怎么運行的呢?

說到信號與槽,大家應該再熟悉不過了,包括我,特別喜歡使用自定義信號與槽,感覺用起來特方便、特棒。。。

經常使用,你能否100%的使用正確?你了解它的高級用法嗎?

1、你是否在多次connect,還發現不了為什么槽函數會執行那N多次。

2、你是否了解disconnect

3、你是否了解connect中的第五個參數 Qt::ConnectionType

關於connect、disconnect信號、槽的使用可參考:Qt之信號與槽。既然談到線程這里需要重點說下Qt::ConnectionType(信號與槽的傳遞方式)

Constant

Value

Description

Qt::AutoConnection

0

自動連接:(默認值)如果信號在接收者所依附的線程內發射,則等同於直接連接。如果發射信號的線程和接受者所依附的線程不同,則等同於隊列連接。

Qt::DirectConnection

1

直接連接:當信號發射時,槽函數將直接被調用。無論槽函數所屬對象在哪個線程,槽函數都在發射信號的線程內執行。

Qt::QueuedConnection

2

隊列連接:當控制權回到接受者所依附線程的事件循環時,槽函數被調用。槽函數在接收者所依附線程執行。也就是說:這種方式既可以在線程內傳遞消息,也可以跨線程傳遞消息

Qt::BlockingQueuedConnection

3

與Qt::QueuedConnection類似,但是會阻塞等到關聯的slot都被執行。這里出現了阻塞這個詞,說明它是專門用來多線程間傳遞消息的。

 

舉例:

MyObject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include 

class MyObject : public QObject
{
    Q_OBJECT

public:
    explicit MyObject(QObject *parent = 0);

public slots:
    void start();
};

#endif // MYOBJECT_H
MyObject.cpp
#include "MyObject.h"
#include 
#include 

MyObject::MyObject(QObject *parent)
    : QObject(parent)
{

}

void MyObject::start()
{
    qDebug() << QString("my object thread id:") << QThread::currentThreadId();
}
main.cpp
#include "MyObject.h"
#include 
#include 
#include 

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

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

    MyObject object;
    QThread thread;
    object.moveToThread(&thread);
    QObject::connect(&thread, SIGNAL(started()), &object, SLOT(start()));
    thread.start();

    return a.exec();
} 
查看運行結果:

  "main thread id:" 0xf08

  "my object thread id:" 0x216c

    顯然主線程與槽函數的線程是不同的(你可以多次嘗試,屢試不爽。。。),因為moveToThread后MyObject所在的線程為QThread,繼上面介紹的thread.start()執行后首先會發射started()信號,也就是說started()信號發射是在次線程中進行的,所以無論采取Qt::AutoConnection、Qt::DirectConnection、Qt::QueuedConnection哪種連接方式,主線程與槽函數的線程都是不同的。

 

1、修改代碼如下:

    MyObject object;
    QThread thread;
    //object.moveToThread(&thread);
    QObject::connect(&thread, SIGNAL(started()), &object, SLOT(start()), Qt::DirectConnection);
    thread.start();
查看運行結果:

  "main thread id:" 0x2688

  "my object thread id:" 0x2110 

    顯然主線程與槽函數的線程是不同的,MyObject所依附的線程為主線程(因為注釋掉了moveToThread),繼上面介紹的Qt::DirectConnection(無論槽函數所屬對象在哪個線程,槽函數都在發射信號的線程內執行)。也就是說started()信號發射是在次線程中進行的,槽函數也是在次線程中進行的,所以主線程與槽函數的線程是不同的。

 

2、修改代碼如下:

    MyObject object;
    QThread thread;
    //object.moveToThread(&thread);
    QObject::connect(&thread, SIGNAL(started()), &object, SLOT(start()), Qt::QueuedConnection);
    thread.start();

查看運行結果:

  "main thread id:" 0x24ec

  "my object thread id:" 0x24ec 

    顯然主線程與槽函數的線程是相同的,繼上面介紹的Qt::QueuedConnection(槽函數在接收者所依附線程執行)。也就是說started()信號發射是在次線程中進行的,但MyObject所依附的線程為主線程(因為注釋掉了moveToThread),所以主線程與槽函數的線程必然是相同的。

 

3、修改代碼如下:

    MyObject object;
    QThread thread;
    //object.moveToThread(&thread);
    QObject::connect(&thread, SIGNAL(started()), &object, SLOT(start()), Qt::AutoConnection);
    thread.start();
查看運行結果:

  "main thread id:" 0x2700

  "my object thread id:" 0x2700 

    顯然主線程與槽函數的線程是相同的,MyObject所依附的線程為主線程(因為注釋掉了moveToThread),繼上面介紹的Qt::AutoConnection(如果信號在接收者所依附的線程內發射,則等同於直接連接。如果發射信號的線程和接受者所依附的線程不同,則等同於隊列連接。)。因為started()信號和MyObject依附的線程不同,所以結果和Qt::QueuedConnection對應的相同,所以主線程與槽函數的線程是相同的。

 

    基本就介紹到這里,QThread使用和上面的大同小異,run里面執行的代碼都是在次線程中,如果是QThead的槽函數,那么結論同上!


免責聲明!

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



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