該文出自:http://www.civilnet.cn/bbs/topicno/78430
使用線程技術進行應用編程的意義在gemfield的文章《從進程到線程》中已經介紹過了,我們就直奔主題:Linux上的線程開發以及Qt對線程的封裝(Linux平台部分)。Linux上的線程API使用的是pthread庫,我們來粗略認識下pthread。
要在程序中使用pthread線程,究竟會用到哪幾部分的功能呢? 1、創建線程是必須的吧,pthread_create(); 2、設置線程的屬性也是需要的吧 3、終止線程的功能也是必須的吧 4、回收線程所占用的資源也是需要的吧 還有? 5、同步是個大的命題!;
上面的第5點要在gemfield的下一篇文章中介紹了。
那么Qt的線程類QThread對外提供了什么接口和功能呢?好像也差不多: 1、start() ,開始一個線程; 2、quit()、terminate(),結束一個線程; 3、setPriority ( Priority priority ),設置調度優先級; 4、setStackSize (uint stackSize),設置線程棧的大小; 5、wait(unsigned long time = ULONG_MAX ),阻塞一個線程直到下面的條件滿足了(類似於POSIX的pthread_join()):
第5點中的wait()意義如下:
*和此線程關聯的另外一個線程運行完畢了; *參數指定的毫秒數過去了之后. 如果時間是LONG_MAX (默認), 那相當於這個條件就作廢了,只能看第一個條件了。
依上面的內容看,Qt的接口和pthread的接口看起來差不多嘛…
事實是怎樣的呢?Qt的庫在Linux平台上的部分究竟是怎么封裝pthread的呢?這一切,先從熟悉pthread的使用開始。
我們把gemfield的《從進程到線程》一文中的示例代碼clone.c拿過來,重名命為gemfield-thread.c(符合gemfield本文的主旋律 ),如下:
*******************gemfield-thread.c********************************* #include <stdio.h> #include <pthread.h> int gemfield =0; void civilnet_cn(){ printf(“gemfield do clone***\n”); scanf(“%d”,gemfield); }
int main(int argc,char **argv) { pthread_t tid; int ret = pthread_create(&tid,NULL,civilnet_cn,NULL); printf(“gemfield do clone…\n”); scanf(“%d”,gemfield); } **************************************************** 編譯: gcc gemfield-thread.c -lpthread -o gemfield-thread 運行: ./gemfield-thread&
第一部分、線程的創建 int pthread_create(pthread_t * thread, const pthread_attr_t * attr,void * (*start_routine)(void *), void *arg);
從gemfield-thread.c中可以看出,創建一個線程用的是pthread_create函數。這個函數的系統調用過程在gemfield的《從進程到線程》中有所描述。
與fork()調用創建一個進程的方法不同,pthread_create()創建的線程並不具備與主線程(即調用pthread_create()的線程)同樣的執行序列,而是使其運行civilnet_cn函數 (pthread_create的第三個參數)。pthread_create將創建的線程的id賦給tid變量(第一個參數,該id同樣可以通過pthread_self()函數來獲得),pthread_create()的返回值表示線程創建是否成功(0為成功,否則不成功,返回非零值並設置errno)。
注意,第二個參數的實參為NULL,表明我們沒有給這個新的線程設置任何的屬性。那么,究竟可以設置什么樣的屬性呢,如果我想要的話? 第二部分、線程屬性:pthread_attr_t結構體以及作用於其上的pthread_attr_*函數族調用 雖然在gemfield-thread.c中,我們創建新線程時第二個參數pthread_attr_t * attr為NULL,但你完全可以充分使用這個參數。
在pthread中,我們不是直接訪問pthread_attr_t結構體,而是通過pthread_attr_*函數族調用來訪問,常見的函數有: ******************************************************* pthread_attr_t attr;//線程屬性結構體
//初始化pthread_attr_t結構體 pthread_attr_init(&attr);
//設置分離狀態,參數默認是PTHREAD_CREATE_JOINABLE, //一旦設置為PTHREAD_CREATE_DETACHED,則該線程不能被使用pthread_join(), //並且線程結束后系統自行釋放它所占用的資源 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//參數有兩種值可供選擇:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED, //前者表示新線程使用顯式指定調度策略和調度參數(即attr中的值), //后者表示繼承調用者線程的值 。參數默認為PTHREAD_EXPLICIT_SCHED。 pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED);
//調度策略,取值有: SCHED_OTHER(正常、非實時), //SCHED_RR(實時、輪轉法)和 SCHED_FIFO(實時、先入先出)三種, //默認為SCHED_OTHER,后兩種調度策略僅對超級用戶有效 。 //運行時可以用過 pthread_setschedparam()來改變。 pthread_attr_setschedpolicy(&attr, sched_policy) //調度策略,取值同上; pthread_attr_getschedpolicy(&attr, &sched_policy)
//改變調度策略,參數為一個struct sched_param結構, //目前僅有一個sched_priority整型變量表示線程的運行優先級。 //這個參數僅當調度策略為實時(即SCHED_RR 或SCHED_FIFO)時才有效 , //並可以在運行時通過pthread_setschedparam()函數來改變,默認為0。 pthread_attr_setschedparam(&attr, &sp)
//設置線程棧的大小 pthread_attr_setstacksize(&attr, stackSize)
//銷毀pthread_attr_t結構體,在pthread_create()執行后調用這個函數 pthread_attr_destroy(&attr) *******************************************************
第三部分、線程的取消和終止
通過pthread_create()創建的線程(用civilnet_cn代表)開始了自己的運行,那civilnet_cn什么時候結束呢?
1、線程civilnet_cn的代碼執行完畢的時候;
這種情況下相當於人類的壽終正寢,非常完美,但還是要分2種情況:civilnet_cn線程創建的時候有沒有使用pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)來設置分離屬性:
a、如果沒有設置的話,終止的線程所占用的資源並不會隨着線程的終止而得到釋放,於是,主線程還要為壽終正寢的線程辦理喪事;不過正如進程之間可以用wait()系統調用來同步終止並釋 放資源一樣,線程之間也有類似機制,那就是pthread_join()。使用pthread_join()來釋放civilnet_cn所占用的資源;
pthread_join(pthread_t thread, void **status)調用的本意是:阻塞主線程(發出這個調用的線程)直到civilnet_cn結束,然后分離civilnet_cn,並將civilnet_cn的返回值放在status 里。
需要注意的是一個線程僅允許唯一的一個線程使用pthread_join()等待它的終止,並且被等待的線程應該處於可join狀態,即非DETACHED狀態。
如果主線程執行了pthread_detach(civilnet_cn),則civilnet_cn線程將處於DETACHED狀態,這使得civilnet_cn線程在結束運行時自行釋放所占用的內存資源,同時也無法由pthread_join() 同步,pthread_detach(civilnet_cn)執行之后,對civilnet_cn請求pthread_join()將返回錯誤。
一個可join的線程所占用的內存僅當有線程對其執行了pthread_join()后才會釋放,因此為了避免內存泄漏,所有線程的終止,要么已設為DETACHED,要么就需要使用pthread_join()來回收 。
b、如果設置了的話,就表明civilnet_cn線程一出生就和主線程分離了,那就啥都不用管,由系統回收civilnet_cn所占用的資源;
2、主線程向civilnet_cn發出pthread_cancel()調用的時候;
a、發送終止信號給civilnet_cn線程,如果成功則返回0,否則為非0值。發送成功並不意味着civilnet_cn會終止 (還要考慮civilnet_cn的狀態以及取消點的位置)。
b、該調用向civilnet_cn線程發出cancel信號,但如何處理cancel信號則由civilnet_cn線程自己決定(由自己的cancel狀態決定,參考下文) ,或者忽略(參考pthread_setcancelstate)、或者立即終止(pthread_setcanceltype)、或者繼續運行至cancel-point(取消點,默認)。
c、默認情況下(pthread_create()創建線程的缺省狀態),civilnet_cn線程接收到cancel信號后 ,是繼續運行至取消點 ,也就是說設置一個cancel狀態,civilnet_cn線程繼續運行至取消點的時候才會退出。
d、那什么是取消點呢?
根據POSIX標准,pthread_join()、pthread_testcancel()、 pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函數以及 read()、write()等會引起阻塞的系統調用都是取消點 ,而其他pthread函數都不會引起取消動作。但是pthread_cancel的手冊頁聲稱,由於LinuxThread庫與C庫結合得不好,因而目前C庫函數都不是取消點 ;但cancel信號會使線程從阻塞的系統調用中退出,並置EINTR錯誤碼 ,因此可以在需要作為 cancel-point的系統調用前后調用pthread_testcancel(),從而達到POSIX標准所要求的目標。比如:
pthread_testcancel(); retcode = read(fd, buffer, length); pthread_testcancel();
e、通過pthread_setcancelstate(int state, int *oldstate) 可以設置civilnet_cn對cancel信號的反應 ,state有兩種值:PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE, 分別表示收到信號后設為CANCLED狀態和忽略CANCEL信號繼續運行;
f、通過pthread_setcanceltype(int type, int *oldtype) 設置civilnet_cn線程取消動作的執行時機,type由兩種取值:PTHREAD_CANCEL_DEFFERED(默認)和 PTHREAD_CANCEL_ASYCHRONOUS, 僅當cancel狀態為Enable時有效 ,分別表示收到cancel信號后繼續運行至下一個取消點再退出和 立即執行取消動作(退出) ;
g、通過pthread_testcancel(void) 檢查civilnet_cn線程是否處於canceld狀態,如果是,則進行取消動作 ,否則直接返回;
3、主線程向civilnet_cn發出pthread_exit()調用的時候;該函數用於退出當前線程,退出之前將調用pthread_cleanup_push,該函數在線程的上層函數中是被隱式調用的;
4、主線程向civilnet_cn發出pthread_kill()調用,並且參數是SIGKILL信號時;調用的時候;哈哈,這一點是騙你的;因為通過pthread_kill調用發送SIGKILL信號時,會導致整個進程終止,不管你是想發給哪個線程;因為SIGKILL這個信號被設計出來的使命就是終止整個進程;
5、發生異常或者某些硬件特性導致的線程終止;這個,我也無能為力。
第五部分:線程終止時的清理
如何保證civilnet_cn線程終止時能順利的釋放掉自己所占用的資源,特別是鎖資源,是一個必須考慮解決的問題。
最經常出現的情形是mutex的使用:civilnet_cn線程為了訪問臨界資源而為其加上鎖,但在訪問過程中civilnet_cn線程被外界取消,如果線程處於響應取消狀態,且采用異步方式響應,或者在打開獨占鎖以前的運行路徑上存在取消點,則該臨界資源將永遠處於鎖定狀態得不到釋放。外界取消操作是不可預見的,因此的確需要一個機制來簡化用於資源釋放的編程。
在POSIX線程API中提供了一個pthread_cleanup_push()/pthread_cleanup_pop()函數對用於自動釋放資源–從pthread_cleanup_push()的調用點到pthread_cleanup_pop()之間的程序段中的終止動作(包括調用pthread_exit()和取消點終止)都將執行pthread_cleanup_push()所指定的清理函數。函數定義如下:
void pthread_cleanup_push(void (*routine) (void *), void *arg) void pthread_cleanup_pop(int execute)
pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的棧結構管理,void routine(void *arg)函數在調用pthread_cleanup_push()時壓入清理函數棧,多次對 pthread_cleanup_push()的調用將在清理函數棧中形成一個函數鏈,在執行該函數鏈時按照壓棧的相反順序彈出。execute參數表示執行到pthread_cleanup_pop()時是否在彈出清理函數的同時執行該函數,為0表示不執行,非0為執行;這個參數並不影響異常終止時清理函數的執行。
pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式實現的,這是pthread.h中的宏定義:
#define pthread_cleanup_push(routine,arg) \ { struct _pthread_cleanup_buffer _buffer; \ _pthread_cleanup_push (&_buffer, (routine), (arg)); #define pthread_cleanup_pop(execute) \ _pthread_cleanup_pop (&_buffer, (execute)); }
可見,pthread_cleanup_push()帶有一個”{“,而pthread_cleanup_pop()帶有一個”}”,因此這兩個函數必須成對出現,且必須位於程序的同一級別的代碼段中才能通過編譯。在下面的例子里 ,當線程在”do some work”中終止時,將主動調用pthread_mutex_unlock(mut),以完成解鎖動作。
pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
pthread_mutex_lock(&mut); /* do some work */ pthread_mutex_unlock(&mut); pthread_cleanup_pop(0);
必須要注意的是,如果線程處於PTHREAD_CANCEL_ASYNCHRONOUS狀態,上述代碼段就有可能出錯,因為CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之間發生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之間發生,從而導致清理函數unlock一個並沒有加鎖的mutex變量,造成錯誤。因此,在使用清理函數的時候,都應該暫時設置成 PTHREAD_CANCEL_DEFERRED模式。為此,POSIX的Linux實現中還提供了一對不保證可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()擴展函數,功能與以下代碼段相當:
{ int oldtype; pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype); pthread_cleanup_push(routine, arg); … pthread_cleanup_pop(execute); pthread_setcanceltype(oldtype, NULL); }
第六部分:線程中特有的線程存儲
線程中特有的線程存儲——Thread Specific Data(TSD) 。線程存儲是什么意思呢?又有什么用?從gemfield的《從進程到線程》中我們知道,在多線程程序中,全局變量可以被所有線程訪問。這在線程間要共享數據時是有意義和靈活的,但是,如果每個線程希望能單獨擁有自己的全局變量,那么就需要使用線程存儲了。表面上看起來這是一個全局變量,所有線程都可以使用它,而它的值在每一個線程中又是單獨存儲的。這就是線程存儲的意義。
下面是線程存儲的具體用法:
1、創建一個類型為 pthread_key_t 類型的變量;
2、調用 pthread_key_create(pthread_key_t *key, void (*destr_function) (void *)) 來創建該變量;該函數有兩個參數,第一個參數就是上面聲明的 pthread_key_t 變量,第二個參數 是一個清理函數,用來在線程釋放該線程存儲的時候被調用。該函數指針可以設成 NULL ,這樣系統將調用默認的清理函數;
不論哪個線程調用pthread_key_create(),所創建的key都是所有線程可訪問的(參見下面的第6步),但各個線程可根據自己的需要往key中填入不同的值 ,這就相當於提供了一個同名而不同值的全局變量。在LinuxThreads的實現中,TSD池用一個結構數組表示:
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };
數據存放與一個32×32的稀疏矩陣中 。同樣,訪問的時候也由key值經過類似計算得到數據所在位置索引,再取出其中內容返回。(說的更為簡單一點就是把一個一維數組模擬成為二維數組,通過除法得到屬於哪一層,通過取余運算得到屬於該層的第幾個元素 。 3、當線程中需要存儲特殊值的時候,可以調用int pthread_setspecific(pthread_key_t key, const void *value); 該函數有兩個參數,第一個為前面聲明的 pthread_key_t 變量,第二個 為 void* 變量,這樣你可以存儲任何類型的值;
4、如果需要取出所存儲的值,調用 void *pthread_getspecific(pthread_key_t key); 該函數的參數為前面提到的 pthread_key_t 變量,該函數返回 void * 類型的值。
5、注銷一個TSD采用如下API:int pthread_key_delete(pthread_key_t key);這個函數並不檢查當前是否有線程正使用該TSD,也不會調用清理函數(destr_function) ,而只是將TSD釋放以供下一次調用 pthread_key_create()使用 。 6、在上面的第2步中有這么一句話:“不論哪個線程調用pthread_key_create(),所創建的key都是所有線程可訪問的(參見下面的第6步)”,這也就是說,對於一個多線程程序來說,只需要有一個這樣的key就可以了。那究竟由哪個線程來創建這個key呢?因為線程的並發性,我們無法預期哪個線程會首先執行;保險的做法就是每一個線程中都有一個pthread_key_create()調用;那怎樣保證一旦key被創建后,其他的線程就別再創建呢?這就是pthread_once()的價值了:
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))
本函數使用初值為PTHREAD_ONCE_INIT的once_control變量保證init_routine()函數在本進程執行序列中僅執行一次。而這個init_routine()在gemfield這里的上下文中就是要調用 pthread_key_create()的函數了:-)
gemfield的本文到了這里,就簡單的說完了pthread的用法了;雖然還沒有說到pthread_mutex_* 、pthread_cond_*、 pthread_rwlock_*的API(它們分別是互斥、條件變量、讀寫鎖,留在gemfield下面的文章中介紹),但我們還是要了解下QThread是怎樣封裝pthread API的(Linux平台上)。 QThread類的結構如下(省略了信號和其他一些東西) ************************************************** class Q_CORE_EXPORT QThread : public QObject { public: static Qt::HANDLE currentThreadId(); static QThread *currentThread(); static int idealThreadCount(); explicit QThread(QObject *parent = 0); enum Priority { …. }; void setPriority(Priority priority); void setStackSize(uint stackSize); void exit(int retcode = 0); QAbstractEventDispatcher *eventDispatcher() const; void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher); public Q_SLOTS: void start(Priority = InheritPriority); void terminate(); void quit(); public: bool wait(unsigned long time = ULONG_MAX); static void sleep(unsigned long); static void msleep(unsigned long); static void usleep(unsigned long); protected: virtual void run(); int exec(); static void setTerminationEnabled(bool enabled = true); private: Q_OBJECT Q_DECLARE_PRIVATE(QThread) static void initialize(); static void cleanup(); friend class QCoreApplication; friend class QThreadData; }; ********************************************************** 看起來QThread類的內部並沒有什么數據成員?那它怎么實現的線程機制?別急,有一個叫作Q_DECLARE_PRIVATE的宏如下: #define Q_DECLARE_PRIVATE(Class) \ inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \ inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \ friend class Class##Private;
以及: template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; } template <typename Wrapper> static inline typename Wrapper::pointer qGetPtrHelper(const Wrapper &p) { return p.data(); }
(等等!上面的qGetPtrHelper()函數是神馬情況?只是把參數原封不動的返回,要它有什么用?) (別急,ptr 可能是一個智能指針 (比如:QScopedPointer) ,如果是這樣的話,是不能給reinterpret_cast作轉換用的,這樣d_func()必須通過data()這個成員函數來訪問智能指針內部的指針: qGetPtrHelper所作的就是提供了一個統一的處理方法。)
所以QThread就成了 class Q_CORE_EXPORT QThread : public QObject { …… private: inline QThreadPrivate* d_func() { return reinterpret_cast<QThreadPrivate *>(d_ptr); } friend class QThreadPrivate; } 其中,d_ptr的類型是QObjectData*,指向的卻是從QObjectData繼承而來的QObjectPrivate*類型的成員(QObjectData是基類,d_ptr相當於是指向派生類對象的基類指針); ************************************************************* QScopedPointer<QObjectData> d_ptr; QObject::QObject(QObjectPrivate &dd, QObject *parent): d_ptr(&dd) //在下文中,QObjectPrivate &dd這個參數傳過來的實參正是QThreadPrivate; *************************************************************** 因為QObjectPrivate是個好東西,來看看它的成員: QObjectPrivate::QObjectPrivate(int version):threadData(0), connectionLists(0), senders(0), currentSender(0), currentChildBeingDeleted(0) { q_ptr = 0; parent = 0; //不是父對象,通過setParent()置位 isWidget = false;//不是一個widget blockSig = false;//是否阻塞信號 wasDeleted = false;// double-delete catcher isDeletingChildren = false;//通過deleteChildren()置位 sendChildEvents = true;//是否把ChildInsert和ChildRemove事件發給父對象 receiveChildEvents = true; postedEvents = 0; extraData = 0; connectedSignals[0] = connectedSignals[1] = 0; metaObject = 0; isWindow = false; }
QObjectData的定義如下: ************************************************************ class QObjectData { public: virtual ~QObjectData() = 0; QObject *q_ptr; QObject *parent; QObjectList children;
uint isWidget : 1; uint blockSig : 1; uint wasDeleted : 1; uint isDeletingChildren : 1; uint sendChildEvents : 1; uint receiveChildEvents : 1; uint isWindow : 1; //for QWindow uint unused : 25; int postedEvents; QMetaObject *metaObject; // assert dynamic }; ************************************************************ QThreadPrivate的定義如下: *********************************************************** class QThreadPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QThread) public: QThreadPrivate(QThreadData *d = 0); ~QThreadPrivate(); mutable QMutex mutex; bool running; bool finished; bool terminated; bool isInFinish; //when in QThreadPrivate::finish bool exited; int returnCode; uint stackSize; QThread::Priority priority; static QThread *threadForId(int id); pthread_t thread_id; QWaitCondition thread_done; static void *start(void *arg); static void finish(void *); QThreadData *data; static void createEventDispatcher(QThreadData *data); }; *********************************************** QThreadData的定義如下: ********************************************** class QThreadData { QAtomicInt _ref; public: QThreadData(int initialRefCount = 1); ~QThreadData(); static QThreadData *current(); static QThreadData *get2(QThread *thread) { Q_ASSERT_X(thread != 0, “QThread”, “internal error”); return thread->d_func()->data; } void ref(); void deref(); QThread *thread; Qt::HANDLE threadId; bool quitNow; int loopLevel; QAbstractEventDispatcher *eventDispatcher; QStack<QEventLoop *> eventLoops; QPostEventList postEventList; bool canWait; QVector<void *> tls; bool isAdopted; }; ********************************************** 了解了QThread的類結構體系后,我們來看看具體的實施吧: 使用Qt的QThread,一般的步驟是: 1、從QThread派生一個類MyThread; 2、在MyThread中重新實施run這個虛函數; 3、實例化一個對象gemfieldThread; 4、調用gemfieldThread->start()開始這個線程; 5、要結束gemfieldThread這個線程,兩種方法:
第一、退出線程的事件循環: gemfieldThread->quit(); gemfieldThread->wait(); gemfieldThread->deleteLater(); 第二、終止線程的運行: gemfieldThread->terminate(); gemfieldThread->wait(); gemfieldThread->deleteLater();
一段代碼演示如下: ******************************************** class MyThread : public QThread { public: void run(); };
void MyThread::run() { QTcpSocket socket; // connect QTcpSocket’s signals somewhere meaningful … socket.connectToHost(hostName, portNumber); exec(); } *********************************** 或者訪問:http://www.civilnet.cn/bbs/topicno/6487 來了解更多Qt線程的用法
下面,就讓gemfield帶領你來看看上面的過程是怎么封裝pthread的:
第一步、構造一個QThread對象: QThread::QThread(QObject *parent): QObject(*(new QThreadPrivate), parent) { Q_D(QThread); d->data->thread = this; } 其中,Q_D宏如下: #define Q_D(Class) Class##Private * const d = d_func() 也即: QThread::QThread(QObject *parent): QObject(*(new QThreadPrivate), parent) { //gemfield是(QThreadPrivate* gemfield = new QThreadPrivate) QThreadPrivate * const d = reinterpret_cast<QThreadPrivate *>(gemfield); d->data->thread = this;//QThreadData的成員QThread*指向自己 }
第二步、start調用究竟發生了什么?start()是怎樣調用到我們在MyThread重新實施的run()函數呢?
在QThread模塊中,start()在Linux平台上的實施是在qthread_unix.cpp中完成的,代碼如下: ***************************************** void QThread::start(Priority priority)//優先級 { Q_D(QThread); QMutexLocker locker(&d->mutex);
if (d->isInFinish) d->thread_done.wait(locker.mutex());
if (d->running) return;
d->running = true; d->finished = false; d->terminated = false; d->returnCode = 0; d->exited = false;
pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
d->priority = priority;
switch (priority) { case InheritPriority: { pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED); break; }
default: { int sched_policy; if (pthread_attr_getschedpolicy(&attr, &sched_policy) != 0) { // failed to get the scheduling policy, don’t bother // setting the priority qWarning(“QThread::start: Cannot determine default scheduler policy”); break; }
int prio; if (!calculateUnixPriority(priority, &sched_policy, &prio)) { // failed to get the scheduling parameters, don’t // bother setting the priority qWarning(“QThread::start: Cannot determine scheduler priority range”); break; }
sched_param sp; sp.sched_priority = prio;
if (pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED) != 0 || pthread_attr_setschedpolicy(&attr, sched_policy) != 0 || pthread_attr_setschedparam(&attr, &sp) != 0) { // could not set scheduling hints, fallback to inheriting them // we’ll try again from inside the thread pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED); d->priority = Priority(priority | ThreadPriorityResetFlag); } break; } }
if (d->stackSize > 0) { int code = pthread_attr_setstacksize(&attr, d->stackSize);
if (code) { qWarning(“QThread::start: Thread stack size error: %s”, qPrintable(qt_error_string(code))); d->running = false; d->finished = false; return; } } int code =pthread_create(&d->thread_id, &attr, QThreadPrivate::start, this); if (code == EPERM) { // caller does not have permission to set the scheduling // parameters/policy pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED); code =pthread_create(&d->thread_id, &attr, QThreadPrivate::start, this); } pthread_attr_destroy(&attr); if (code) { qWarning(“QThread::start: Thread creation error: %s”, qPrintable(qt_error_string(code))); d->running = false; d->finished = false; d->thread_id = 0; } } ************************************************ 在上面的代碼中,我們終於看到了gemfield在上文中講解過的pthread API了。其中:
int code =pthread_create(&d->thread_id, &attr, QThreadPrivate::start, this);
將QThreadPrivate::start作為線程的實體,我們再看看QThreadPrivate::start是怎么實施的(this指針參考:http://civilnet.cn/bbs/topicno/71261,下面的代碼用到了): ********************************************** void *QThreadPrivate::start(void *arg) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); pthread_cleanup_push(QThreadPrivate::finish, arg); //參數arg是this QThread *thr = reinterpret_cast<QThread *>(arg); QThreadData *data = QThreadData::get2(thr);
// do we need to reset the thread priority? if (int(thr->d_func()->priority) & ThreadPriorityResetFlag) { thr->setPriority(QThread::Priority(thr->d_func()->priority & ~ThreadPriorityResetFlag)); }
data->threadId = (Qt::HANDLE)pthread_self(); set_thread_data(data);
data->ref(); { QMutexLocker locker(&thr->d_func()->mutex); data->quitNow = thr->d_func()->exited; }
if (data->eventDispatcher) // custom event dispatcher set? data->eventDispatcher->startingUp(); else createEventDispatcher(data);
emit thr->started(); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_testcancel(); //執行重新實施的run虛函數 thr->run();
pthread_cleanup_pop(1);
return 0; } **********************************************
第三部分:gemfieldThread->quit()是怎么實施的呢?
首先明白quit是干什么的?當這個線程有自己的事件循環的話,那么這些事件循環全部退出;而如果沒有事件循環的話,則什么也不干; quit()調用的是exit(),而exit()的實施如下:
void QThread::exit(int returnCode) { Q_D(QThread); QMutexLocker locker(&d->mutex); d->exited = true; d->returnCode = returnCode; d->data->quitNow = true; for (int i = 0; i < d->data->eventLoops.size(); ++i) { QEventLoop *eventLoop = d->data->eventLoops.at(i); eventLoop->exit(returnCode); } }
第四部分:gemfieldThread->terminate()是怎么運行的?
這個函數終止一個線程的執行,但並不是立即被執行的,還要取決於操作系統的調度策略;看過了gemfield本文的開頭部分,你這里就會更明白;在調用terminate() 之后使用 QThread::wait() 來同步終止. ************************************************************ void QThread::terminate() { Q_D(QThread); QMutexLocker locker(&d->mutex);
if (!d->thread_id) return;
int code = pthread_cancel(d->thread_id); if (code) { qWarning(“QThread::start: Thread termination error: %s”, qPrintable(qt_error_string((code)))); } else { d->terminated = true; } } ************************************************************* 哈哈,又看見我們熟悉的pthread的API了。
第五部分:上面屢次提到的gemfieldThread->wait()又是怎樣同步的呢?
wait()函數的作用參考本文的開頭部分。 ********************************************************* bool QThread::wait(unsigned long time) { Q_D(QThread); QMutexLocker locker(&d->mutex);
if (d->thread_id == pthread_self()) { qWarning(“QThread::wait: Thread tried to wait on itself”); return false; }
if (d->finished || !d->running) return true;
while (d->running) { if (!d->thread_done.wait(locker.mutex(), time))//參考QWaitCondition::wait() return false; } return true; } *******************************************************
最后:說說QThreadData,在Qt的事件循環中用的更多,但因為底層實現和本文有關,就放在這里說說了:
QThreadData *data = QThreadData::current();
而QThreadData::current()是怎么實現的呢?
QThreadData *QThreadData::current() { QThreadData *data = get_thread_data(); if (!data) { data = new QThreadData; QT_TRY { set_thread_data(data); data->thread = new QAdoptedThread(data); } QT_CATCH(…) { clear_thread_data(); data->deref(); data = 0; QT_RETHROW; } data->deref(); data->isAdopted = true; data->threadId = (Qt::HANDLE)pthread_self(); if (!QCoreApplicationPrivate::theMainThread) QCoreApplicationPrivate::theMainThread = data->thread; } return data; } 也就是當前程序中盡管有多個線程或者事件循環,但實際上只維護了1個QThreadData結構體,但這個結構體在不同的線程中對應着自己的值(有自己的索引),而這個QThreadData靠的是set_thread_data(data)來初始化,而靠get_thread_data()來獲得;
再來看看這兩者的實現:
static QThreadData *get_thread_data() { #ifdef HAVE_TLS return currentThreadData; #else pthread_once(¤t_thread_data_once, create_current_thread_data_key); return reinterpret_cast<QThreadData *>(pthread_getspecific(current_thread_data_key)); #endif }
static void set_thread_data(QThreadData *data) { #ifdef HAVE_TLS currentThreadData = data; #endif pthread_once(¤t_thread_data_once, create_current_thread_data_key); pthread_setspecific(current_thread_data_key, data); } //多熟悉的pthread調用呀!