QThread 的使用方法及函數解析


近日,使用QThread,一些問題百思不得其解,看過大牛的文章,恍然大悟啊。

原文 http://hi.baidu.com/dbzhang800/item/c14c97dd15318d17e1f46f41

 

在文章開始之前加注一點,為和我一樣Qt水平不高的朋友提醒一下。QThread::wait(),一直以來我以為它阻塞的是QThread對象,可是我現在明白,原來阻塞的是這個對象所在的線程(通常是主線程)。

bool QThread::wait ( unsigned long time = ULONG_MAX )


Blocks the thread until either of these conditions is met:
The thread associated with this QThread object has finished execution (i.e. when it returns from run()). This function will return true if the thread has finished. It also returns true if the thread has not been started yet.
time milliseconds has elapsed. If time is ULONG_MAX (the default), then the wait will never timeout (the thread must return from run()). This function will return false if the wait timed out.

以下紅色部分為我添加。

 

起源

昨天不小心看到Qt開發人員( Bradley T. Hughes)Blog中的一片文章  you are-doing-it-wrong 。 結果看得頭昏腦脹:好歹也自學了近1年的Qt,也一直很小心、很認真地閱讀Qt和manual和例子等資料,卻被突然告知,QThread的正確使用方法是一種自己從沒見過,而且Qt manual、example、書籍中都沒有提到過的一種方法。到底怎么了... 

莫非manual、exmaple以及資料中的介紹都是錯的??

認真看看其他的人的評論,總算理清了一點頭緒。所有事情源於 QThread 的事件循環!


QThread 的兩種使用方法


1. 不使用事件循環。這是官方的 Manual 、example 以及相關書籍中都介紹的一種的方法。
a. 子類化 QThread
b. 重載 run 函數,run函數內有一個 while 或 for 的死循環
c. 設置一個標記為來控制死循環的退出。
如果使用這一方法,QThread::quit()沒有效果。因為這個線程根本就不需要事件循環。這種情況想退出,直接使用QT很不推薦的terminate().


2. 使用事件循環。(博客  you are-doing-it-wrong 批駁的就是這種情況下的 一種用法。)

a. 子類化 QThread,
b. 重載 run 使其調用 QThread::exec() 
c. 並為該類定義信號和槽,這樣一來,由於槽函數並不會在新開的 thread 運行,很多人為了解決這個問題在構造函數中調用  moveToThread(this); 

而爭論和不解正是這樣的一條語句造成的。

Bradley T. Hughes 給出說明是:  QThread 應該被看做是操作系統線程的接口或控制點,而不應該包含需要在新線程中運行的代碼。需要運行的代碼應該放到一個QObject的子類中,然后將該子類的對象moveToThread到新線程中

另外:
在Qt4.3(包括)之前,run 是虛函數,必須子類化QThread來實現run函數。
而從Qt4.4開始, qthreads-no-longer-abstract    ,run 默認調用 QThread::exec() 。這樣一來不需要子類化 QThread 了,只需要子類化一個 QObject 就夠了,這正是被 Bradley T. Hughes推薦的方法。


終於看懂了,但
不管怎么說,都應該是 QThread 當初的設計導致的這種問題,而所有文檔和例子中都沒有提到該如何使用Qthread 進一步加劇了對QThread的這種誤用。

另注:1.QThread對象從建立起就是活躍的,所以大牛Bradley T. Hughes把QObject對象移動到QThread中,對QObject的操作是完全合理合法合邏輯的。
2.既然使用了多線程,就必須考慮互斥問題,QThread的所有slot函數都是可多重入和不安全的(具體參見QT的可重入和線程安全)。而且在此之外,除了GUI類對象必須在主進程(不可重入,從而保證了線程安全),互斥鎖一類的類可重入和線程安全外。所以的QObject對象都不是線程安全的,換句話說,在主線程內為單線程設計的QObject子類對象,如果沒有對其slot函數做互斥處理,就會出現因signal調用而反復重入某個slot函數的情況,反而成了多線程。(C++對象,由於是順序調用,所以在單線程下不會出現這個問題)。這時需要依據情況考慮互斥鎖。
3.使用大牛Bradley T. Hughesr的方法把QObject對象移動到QThread中,要使用signal+slot的方式來調用函數,這樣的話,通過QT消息機制,QObject被調用的函數是在線程內執行。如果直接(QObject對象).abc()的話,這個成員函數是在主進程內執行,可能會出現"QObject::killTimer: timers cannot be stopped from another thread"的運行錯誤。

相關鏈接:

http://labs.qt.nokia.com/blogs/2010/06/17/youre-doing-it-wrong/
http://labs.qt.nokia.com/blogs/2006/12/04/threading-without-the-headache/
http://labs.qt.nokia.com/blogs/2007/07/05/qthreads-no-longer-abstract/
http://gitorious.org/qthreadhowto/qthreadhowto/trees/master
http://blog.exys.org/entries/2010/QThread_affinity.html
http://thesmithfam.org/blog/2010/02/07/talking-to-qt-threads/
 
轉自:http://blog.csdn.net/lainegates/article/details/9701215
 
 

QThread實例代表一個線程,我們可以重新實現QThread::run(),要新建一個線程,我們應該先繼承QThread並重新實現run()函數。

需要注意的是:

1.必須在創建QThread對象之前創建 QApplication (或QCoreApplication)對象。

2. QCoreApplication::exec() 必須只能從主線程調用,不能從QThread線程調用。

class MyThread : public  QThread

{

       Q_OBJECT

public:

       MyThread(QObject *parent = NULL);

       ~MyThread();

 

protected:

       voidrun();

};

 

void MyThread::run()

{

       for( int count = 0; count < 20; count++ )

       {

              sleep( 1 );

       }

       qDebug( "finish!");

}

我們可以在另外的函數這樣調用:

MyThread thread;

thread.start();

thread.wait();   //必須要加的語句,等待thread結束。

當然我們先開的線程可能是要運行很久,會卡住主線程,使用QeventLoop就可以輕松解決此類問題:

MyThread thread;

thread.start();

QeventLoop;

connect(&thread,SIGNAL(finished ()),&eventLoop,SLOT(quit()));

thread.wait(1);

eventLoop.exec();

 

接下來我們看QThread的其他屬性和函數:

Qthread的優先級屬性:Priority指示系統如何調度線程。

Constant

Value

Description

QThread::IdlePriority

0

scheduled only when no other threads are running.

QThread::LowestPriority

1

scheduled less often than LowPriority.

QThread::LowPriority

2

scheduled less often than NormalPriority.

QThread::NormalPriority

3

the default priority of the operating system.

QThread::HighPriority

4

scheduled more often than NormalPriority.

QThread::HighestPriority

5

scheduled more often than HighPriority.

QThread::TimeCriticalPriority

6

scheduled as often as possible.

QThread::InheritPriority

7

use the same priority as the creating thread. This is the default.

0到6的優先級跟windows線程相對應的,就多了一個InheritPriority。

非靜態成員函數:

void

exit ( int returnCode = 0 )

該函數告訴線程退出事件循環和返回代碼returnCode ;通常返回0意味着成功,非0意味着錯誤。

需要注意的是,該函數不像C語言的庫函數exit一樣返回到調用者。除非再次調用QThread::exec(),否則不會再執行任何事件循環。如果QThread::exec()的事件循環沒有運行,接着的QThread::exec()將會立即返回。

 

這兩個函數看名字就知道意思:

bool QThread::isFinished ()const

如果線程結束,則返回true,否則返回false。

 

bool QThread::isRunning ()const

如果線程還在運行則返回true,否則返回false

 

void QThread::setPriority ( Priority priority )

該函數設置了正在運行的線程的優先級。如果線程沒有運行,函數什么也不做,直接返回。跟蹤源碼發現setPriority 的源碼部分如下:

  if(!d->running)

 {

        qWarning("QThread::setPriority:Cannot set priority, thread is not running");

        return;

 }

如果線程沒有運行,函數只輸出一個警告便返回了。

需要注意的是:參數不能是InheritPriorty。

case InheritPriority:

default:

        qWarning("QThread::setPriority:Argument cannot be InheritPriority");

        return;

如果是InheritPriorty,函數也只輸出一個警告便返回了。

優先級參數的影響依賴於操作系統的調度策略。特別是,在不支持線程優先級的系統(如Linux)優先級參數將被忽略。

 

void QThread::setStackSize ( uint stackSize )

該函數設置線程堆棧的最大值。

需要注意的是:

1.必須在線程啟動之前設置

2. 雖然參數是uint類型,傳入負數的話會自動轉換。

 3.大部分的操作系統都設置了線程堆棧的最大最小值限制。如果超出了限制線程可能運行失敗。

驗證了一下,當我傳-1進去,stackSize值變成了4294967295。Start函數里調用beginthreadex創建新線程。Beginthreadex里調用CreateThread這是大部分用過windows多線程的程序員都了解的。創建失敗則輸出警告並返回。

  d->handle = (Qt::HANDLE)_beginthreadex(NULL, d->stackSize, QThreadPrivate::start,

                                            this, CREATE_SUSPENDED, &(d->id));

 

    if(!d->handle) {

        qErrnoWarning(errno, "QThread::start: Failed to create thread");

        d->running = false;

        d->finished = true;

        return;

    }

 

 

uint QThread::stackSize () const

該函數返回線程堆棧最大值,如果調用setStackSize()設置最大值的話,返回的就是設置的值,否則返回0;

 

bool QThread::wait ( unsigned long time =ULONG_MAX )

該函數阻塞線程直到滿足以下條件之一;

1.      線程已經執行結束。如果線程結束,該函數返回true。如果線程沒被啟動也會返回true。

2.      Time毫秒時間過去了。如果time 為 ULONG_MAX,該函數永遠不會超時。如果超時,函數返回false。

 

槽函數:

void QThread::quit ()

函數退出事件循環,返回代碼為0.與調用exit(0).一樣的效果。如果線程沒有事件循環則什么也不做。

 

void QThread::start ( Priority priority =InheritPriority ) [slot]

通過調用開始run(),執行線程。如果線程已經在運行,則什么也不做。

看一下源碼:

Start()調用:

d->handle =(Qt::HANDLE) _beginthreadex(NULL, d->stackSize, QThreadPrivate::start, this,CREATE_SUSPENDED, &(d->id));

創建線程。

 

unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSEQThreadPrivate::start(void *arg)

{

    QThread *thr = reinterpret_cast<QThread*>(arg);

    QThreadData *data = QThreadData::get2(thr);

。。。。。

。。。。。。          //中間省略,刪掉了

       emitthr->started();       //發送started信號。

       QThread::setTerminationEnabled(true);

       thr->run();              //運行我們重新實現的代碼。

 

       finish(arg);              //該函數發送finished();信號。

    return 0;

}

 

void QThread::terminate () 

終止線程的運行。線程可能不會理解被終止,這依賴於操作系統的調用策略。在terminate()之后調用QThread::wait()同步終止。

終止線程之后,所有等待線程結束的線程都會被喚醒。

void QThread::terminate()

{

    Q_D(QThread);

    QMutexLocker locker(&d->mutex);

    if(!d->running)

        return;

    if(!d->terminationEnabled) {

        d->terminatePending = true;

        return;

    }

    TerminateThread(d->handle, 0);

    d->terminated = true;

    QThreadPrivate::finish(this, false);            //finish函數發送了finished()信號。從而會喚醒所有等待的線程。

}

除非真的有必要,不然不要輕易調用該函數。

 

QThread * QThread::currentThread () 

函數返回當前正在執行的線程。

 

Qt::HANDLE QThread::currentThreadId ()

注意:

返回當前運行的線程的句柄。該函數返回的句柄只能內部使用,不能在其他任何程序的代碼中使用。

在windows系統,該返回值是當前線程的偽句柄。不能用作數值比較,函數返回Win32 函數 getCurrentThreadId()返回的DWORD值。而不是 Win32 函數 getCurrentThread()返回的HANDLE值。

 

int QThread::idealThreadCount ()

函數返回系統可以運行的理想的線程數。

int QThread::idealThreadCount()

{

    SYSTEM_INFO sysinfo;

    GetSystemInfo(&sysinfo);

    returnsysinfo.dwNumberOfProcessors;    //返回系統中的處理器的數目。

}

 

void QThread::yieldCurrentThread ()

放棄當前線程轉到另外可執行的線程,有系統決定轉到哪個線程。

 

void QThread::sleep ( unsigned long secs )

void QThread::msleep ( unsigned long msecs ) 

void QThread::usleep ( unsigned long usecs )

這三個函數是讓線程進入休眠狀態。它們內部都是調用了windows API Sleep。

 

 

void QThread::setTerminationEnabled ( bool enabled =true ) 

設置是否可以終止當前線程。

 

轉自:http://blog.csdn.net/hai200501019/article/details/9535069


免責聲明!

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



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