1. pthread線程概念 Linux系統(tǒng)下的多線程遵循POSIX線程接口,稱為pthread。編寫Linux下的多線程程序,需要使用頭文件pthread.h,連接時(shí)需要使用庫(kù)libpthread.a與vxworks上任務(wù)的概念類似,都是調(diào)度的最小單元,都有共享的堆、棧、代碼區(qū)、全局變量等。 2. 創(chuàng)建線程 int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg) thread:返回創(chuàng)建的線程的ID attr:線程屬性,調(diào)度策略、優(yōu)先級(jí)等都在這里設(shè)置,如果為NULL則表示用默認(rèn)屬性 start_routine:線程入口函數(shù),可以返回一個(gè)void*類型的返回值,該返回值可由pthread_join()捕獲 arg:傳給start_routine的參數(shù), 可以為NULL 返回值:成功返回0 3. 設(shè)置線程屬性 線程屬性通過attr進(jìn)行設(shè)置。 設(shè)置與查詢attr結(jié)構(gòu)的為pthread_attr_get***()與pthread_attr_set***()兩個(gè)函數(shù)系列。 設(shè)置與查詢線程參數(shù)的為pthread_get***()與pthread_set***()兩個(gè)函數(shù)系列。也可以在創(chuàng)建時(shí)通過pthrea_create傳入?yún)?shù)。 注:有些必須在線程創(chuàng)建時(shí)設(shè)置,如調(diào)度策略。 3.1. 調(diào)度策略 調(diào)度策略有三種: SCHED_OTHER:非實(shí)時(shí)、正常 SCHED_RR:實(shí)時(shí)、輪詢法 SCHED_FIFO:實(shí)時(shí)、先入先出,與vxworks的調(diào)度機(jī)制一致 例程: pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setschedpolicy(&attr, SCHED_FIFO);//sched_policy 3.2. 優(yōu)先級(jí) 線程優(yōu)先級(jí)支持兩種設(shè)置方式,一種是創(chuàng)建時(shí)設(shè)置,另一種是創(chuàng)建后動(dòng)態(tài)設(shè)置 例程: pthread_attr_setschedparam(&attr, new_priority); 如果是靜態(tài)設(shè)置,則之后會(huì)用該屬性創(chuàng)建任務(wù),如果是動(dòng)態(tài)設(shè)置,則使用下列函數(shù)設(shè)置: pthread_attr_setschedparam(&attr, &task->prv_priority); pthread_attr_getschedparam(&attr, &schedparam); schedparam.sched_priority = new_priority; pthread_attr_setschedparam(&attr, &schedparam); pthread_setschedparam(pthrid, sched_policy, &schedparam); 3.3. 脫離同步 Pthread_join()函數(shù)可以使主線程與子線程保持同步。如果設(shè)置了detachstate狀態(tài),則pthread_join()會(huì)失效,線程會(huì)自動(dòng)釋放所占用的資源。線程的缺省狀態(tài)為PHREAD_CREATE_JOINABLE狀態(tài),線程運(yùn)行起來后,一旦被設(shè)置為PTHREAD_CREATE_DETACH狀態(tài),則無法再恢復(fù)到joinable狀態(tài)。 例程: pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED);、 3.4. 調(diào)度繼承 線程A創(chuàng)建了線程B,則線程B的調(diào)度策略是與線程A的調(diào)度策略與線程B的繼承策略有關(guān)的。如果線程B繼承策略為PTHREAD_INHERIT_SCHED,則線程B的調(diào)度策略與線程A相同;如果線程B繼承策略為PTHREAD_EXPLICIT_SCHE,則線程B的調(diào)度策略由attr決定。 pthread_attr_setdetachstate (&attr,PTHREAD_CREATE_DETACHED); 4. 線程取消 4.1. 線程取消定義 Pthread線程可以通過發(fā)送取消請(qǐng)求的方式終止一個(gè)線程的運(yùn)行。 取消線程的操作主要應(yīng)用于下列場(chǎng)景中:有一個(gè)線程在使用select監(jiān)控網(wǎng)口,主控線程此時(shí)接到了用戶的通知,要放棄監(jiān)聽,則主控線程會(huì)向監(jiān)聽線程發(fā)送取消請(qǐng)求。 Linux的pthread線程接收到取消請(qǐng)求時(shí),并不會(huì)立刻終止線程,而是要等到取消點(diǎn)時(shí)才會(huì)結(jié)束任務(wù)。這樣我們可以為取消點(diǎn)建立某些特殊的處理。Select是取消點(diǎn),所以可以退出。 4.2. 取消點(diǎn) Vxworks可以在任意位置殺死任務(wù),這樣做導(dǎo)致任務(wù)被殺死后的位置不可控,所以vxworks中不會(huì)很少使用taskDeleteHook。 Pthread規(guī)定了取消點(diǎn)的概念。不論線程何時(shí)收到取消請(qǐng)求,都只能在取消點(diǎn)上才能取消線程。這就保證了風(fēng)險(xiǎn)的可控。 Pthread標(biāo)準(zhǔn)指定了以下幾個(gè)取消點(diǎn): pthread_testcancel 所有調(diào)度點(diǎn),如pthread_cond_wait、sigwait、select、sleep等 根據(jù)POSIX標(biāo)準(zhǔn),read()、write()等會(huì)引起阻塞的系統(tǒng)調(diào)用都是Cancelation-point,而其他pthread函數(shù)都不會(huì)引起Cancelation動(dòng)作。但是pthread_cancel的手冊(cè)頁(yè)聲稱,由于LinuxThread庫(kù)與C庫(kù)結(jié)合得不好,因而目前C庫(kù)函數(shù)都不是Cancelation-point,因此可以在需要作為Cancelation-point的系統(tǒng)調(diào)用前后調(diào)用pthread_testcancel(),從而達(dá)到POSIX標(biāo)準(zhǔn)所要求的目標(biāo),即如下代碼段: pthread_testcancel(); retcode = read(fd, buffer, length); pthread_testcancel(); 這段代碼可以保證read附近有取消點(diǎn),但是否有可能會(huì)卡在read中無法返回呢? 如果線程處于無限循環(huán)中,且循環(huán)體內(nèi)沒有執(zhí)行至取消點(diǎn)的必然路徑,則線程無法由外部其他線程的取消請(qǐng)求而終止。因此在這樣的循環(huán)體的必經(jīng)路徑上應(yīng)該加入pthread_testcancel()調(diào)用。 4.3. 線程取消函數(shù) int pthread_cancel(pthread_t thread) 發(fā)送終止信號(hào)給thread線程,如果成功則返回0,否則為非0值。發(fā)送成功并不意味著thread會(huì)終止。 int pthread_setcancelstate(int state, int *oldstate) 設(shè)置本線程對(duì)Cancel信號(hào)的反應(yīng),state有兩種值:PTHREAD_CANCEL_ENABLE(缺?。┖?span lang="EN-US">PTHREAD_CANCEL_DISABLE,分別表示收到信號(hào)后設(shè)為CANCLED狀態(tài)和忽略CANCEL信號(hào)繼續(xù)運(yùn)行;old_state如果不為NULL則存入原來的Cancel狀態(tài)以便恢復(fù)。 int pthread_setcanceltype(int type, int *oldtype) 設(shè)置本線程取消動(dòng)作的執(zhí)行時(shí)機(jī),type由兩種取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,僅當(dāng)Cancel狀態(tài)為Enable時(shí)有效,分別表示收到信號(hào)后繼續(xù)運(yùn)行至下一個(gè)取消點(diǎn)再退出和立即執(zhí)行取消動(dòng)作(退出);oldtype如果不為NULL則存入原來的取消動(dòng)作類型值。 void pthread_testcancel(void) 檢查本線程是否處于Canceld狀態(tài),如果是,則進(jìn)行取消動(dòng)作,否則直接返回。 4.4. 例程
結(jié)果: Start: 2222cond_wait:abc=0x22c29a05 2222cond_wait:abc=0x47b49007 Cancel point2222cond_wait:abc=0x6c9de3ad 2222cond_wait:abc=0x6c9de3ad 2222cond_wait:abc=0x6c9de3ad 如果不加取消點(diǎn)pthread_testcancel(),線程1無法退出。 5. 線程終止方式 線程終止有兩種情況(不考慮進(jìn)程),一種是線程主體函數(shù)return時(shí),線程會(huì)自動(dòng)終止,此時(shí)的退出是可預(yù)知的;另一種是其它線程向通以進(jìn)程下的線程發(fā)送取消請(qǐng)求,線程會(huì)根據(jù)情況判斷是否終止,此時(shí)的終止是不可預(yù)知的。 Vxworks中的任務(wù)與此類似。任務(wù)的主體函數(shù)return時(shí),任務(wù)會(huì)自動(dòng)終止;其它任務(wù)調(diào)用taskDelete()可以殺死任意一個(gè)任務(wù)。TaskDelete()并不安全,因?yàn)槿蝿?wù)可能被殺死在任意一個(gè)時(shí)刻。 5.1. 線程終止時(shí)的清理 不論是可預(yù)見的線程終止還是異常終止,都會(huì)存在資源釋放的問題,在不考慮因運(yùn)行出錯(cuò)而退出的前提下,如何保證線程終止時(shí)能順利的釋放掉自己所占用的資源,特別是鎖資源,就是一個(gè)必須考慮解決的問題。 最經(jīng)常出現(xiàn)的情形是資源獨(dú)占鎖的使用:線程為了訪問臨界資源而為其加上鎖,但在訪問過程中被外界取消,如果線程處于響應(yīng)取消狀態(tài),且采用異步方式響應(yīng),或者在打開獨(dú)占鎖以前的運(yùn)行路徑上存在取消點(diǎn),則該臨界資源將永遠(yuǎn)處于鎖定狀態(tài)得不到釋放。外界取消操作是不可預(yù)見的,因此的確需要一個(gè)機(jī)制來簡(jiǎn)化用于資源釋放的編程。 在POSIX線程API中提供了一個(gè)pthread_cleanup_push()/pthread_cleanup_pop()函數(shù)對(duì)用于自動(dòng)釋放資源,相當(dāng)于是增加了一個(gè)析構(gòu)函數(shù)。從pthread_cleanup_push()的調(diào)用點(diǎn)到pthread_cleanup_pop()之間的程序段中的終止動(dòng)作(包括調(diào)用pthread_exit()和取消點(diǎn)終止)都將執(zhí)行pthread_cleanup_push()所指定的清理函數(shù)。 API定義如下:
pthread_cleanup_push()/pthread_cleanup_pop() 采用先入后出的棧結(jié)構(gòu)管理,void routine(void *arg)函數(shù)在調(diào)用pthread_cleanup_push()時(shí)壓入清理函數(shù)棧,多次對(duì)pthread_cleanup_push()的調(diào)用將在清理函數(shù)棧中形成一個(gè)函數(shù)鏈,在執(zhí)行該函數(shù)鏈時(shí)按照壓棧的相反順序彈出。 execute參數(shù)表示執(zhí)行到pthread_cleanup_pop()時(shí)是否在彈出清理函數(shù)的同時(shí)執(zhí)行該函數(shù),為0表示不執(zhí)行,非0為執(zhí)行;這個(gè)參數(shù)并不影響異常終止時(shí)清理函數(shù)的執(zhí)行。 這兩個(gè)其實(shí)是宏,必須成對(duì)出現(xiàn),否則會(huì)編譯不過。 編譯。在下面的例子里,當(dāng)線程在"do some work"中終止時(shí),將主動(dòng)調(diào)用pthread_mutex_unlock(mut),以完成解鎖動(dòng)作。
必須要注意的是,如果線程處于PTHREAD_CANCEL_ASYNCHRONOUS狀態(tài),上述代碼段就有可能出錯(cuò),因?yàn)?/span>CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之間發(fā)生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之間發(fā)生,從而導(dǎo)致清理函數(shù)unlock一個(gè)并沒有加鎖的mutex變量,造成錯(cuò)誤。因此,在使用清理函數(shù)的時(shí)候,都應(yīng)該暫時(shí)設(shè)置成PTHREAD_CANCEL_DEFERRED模式。為此,pthread中還提供了一對(duì)不保證可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()擴(kuò)展函數(shù),功能與以下代碼段相當(dāng):
5.2. 線程終止的同步及其返回值 一般情況下,進(jìn)程中各個(gè)線程的運(yùn)行都是相互獨(dú)立的,線程的終止并不會(huì)通知,也不會(huì)影響其他線程,終止的線程所占用的資源也并不會(huì)隨著線程的終止而得到釋放。正如進(jìn)程之間可以用wait()系統(tǒng)調(diào)用來同步終止并釋放資源一樣,線程之間也有類似機(jī)制,那就是pthread_join()函數(shù)。
pthread_join()的調(diào)用者將掛起并等待th線程終止,retval是pthread_exit()調(diào)用者線程(線程ID為th)的返回值,如果thread_return不為NULL,則*thread_return=retval。需要注意的是一個(gè)線程僅允許唯一的一個(gè)線程使用pthread_join()等待它的終止,并且被等待的線程應(yīng)該處于可join狀態(tài),即非DETACHED狀態(tài)。 如果進(jìn)程中的某個(gè)線程執(zhí)行了pthread_detach(th),則th線程將處于DETACHED狀態(tài),這使得th線程在結(jié)束運(yùn)行時(shí)自行釋放所占用的內(nèi)存資源,同時(shí)也無法由pthread_join()同步,pthread_detach()執(zhí)行之后,對(duì)th請(qǐng)求pthread_join()將返回錯(cuò)誤。 一個(gè)可join的線程所占用的內(nèi)存僅當(dāng)有線程對(duì)其執(zhí)行了pthread_join()后才會(huì)釋放,因此為了避免內(nèi)存泄漏,所有線程的終止,要么已設(shè)為DETACHED,要么就需要使用pthread_join()來回收。 5.3. 關(guān)于ptread_exite()和return 理論上說,pthread_exit()和線程宿體函數(shù)退出的功能是相同的,函數(shù)結(jié)束時(shí)會(huì)在內(nèi)部自動(dòng)調(diào)用pthread_exit()來清理線程相關(guān)的資源。但實(shí)際上二者由于編譯器的處理有很大的不同。 在進(jìn)程主函數(shù)(main())中調(diào)用pthread_exit(),只會(huì)使主函數(shù)所在的線程(可以說是進(jìn)程的主線程)退出;而如果是return,編譯器將使其調(diào)用進(jìn)程退出的代碼(如_exit()),從而導(dǎo)致進(jìn)程及其所有線程結(jié)束運(yùn)行。 其次,在線程宿主函數(shù)中主動(dòng)調(diào)用return,如果return語(yǔ)句包含在pthread_cleanup_push()/pthread_cleanup_pop()對(duì)中,則不會(huì)引起清理函數(shù)的執(zhí)行,反而會(huì)導(dǎo)致segment fault。 5.4. 判斷是否為同一個(gè)線程 int pthread_equal(pthread_t thread1, pthread_t thread2) 判斷兩個(gè)線程描述符是否指向同一線程。在LinuxThreads中,線程ID相同的線程必然是同一個(gè)線程,因此,這個(gè)函數(shù)的實(shí)現(xiàn)僅僅判斷thread1和thread2是否相等。 5.5. 僅執(zhí)行一次的操作 int pthread_once(pthread_once_t *once_control, void (*init_routine) (void)) 本函數(shù)使用初值為PTHREAD_ONCE_INIT的once_control變量保證init_routine()函數(shù)在本進(jìn)程執(zhí)行序列中僅執(zhí)行一次。這個(gè)類似與線程的構(gòu)造函數(shù)。
once_run()函數(shù)僅執(zhí)行一次,且究竟在哪個(gè)線程中執(zhí)行是不定的,盡管pthread_once(&once,once_run)出現(xiàn)在兩個(gè)線程中。 LinuxThreads使用互斥鎖和條件變量保證由pthread_once()指定的函數(shù)執(zhí)行且僅執(zhí)行一次,而once_control則表征是否執(zhí)行過。如果once_control的初值不是PTHREAD_ONCE_INIT(LinuxThreads定義為0),pthread_once()的行為就會(huì)不正常。在LinuxThreads中,實(shí)際"一次性函數(shù)"的執(zhí)行狀態(tài)有三種:NEVER(0)、IN_PROGRESS(1)、DONE(2),如果once初值設(shè)為1,則由于所有pthread_once()都必須等待其中一個(gè)激發(fā)"已執(zhí)行一次"信號(hào),因此所有pthread_once()都會(huì)陷入永久的等待中;如果設(shè)為2,則表示該函數(shù)已執(zhí)行過一次,從而所有pthread_once()都會(huì)立即返回0。 pthread_kill_other_threads_np() void pthread_kill_other_threads_np(void) 這個(gè)函數(shù)是LinuxThreads針對(duì)本身無法實(shí)現(xiàn)的POSIX約定而做的擴(kuò)展。POSIX要求當(dāng)進(jìn)程的某一個(gè)線程執(zhí)行exec*系統(tǒng)調(diào)用在進(jìn)程空間中加載另一個(gè)程序時(shí),當(dāng)前進(jìn)程的所有線程都應(yīng)終止。由于LinuxThreads的局限性,該機(jī)制無法在exec中實(shí)現(xiàn),因此要求線程執(zhí)行exec前手工終止其他所有線程。pthread_kill_other_threads_np()的作用就是這個(gè)。 需要注意的是,pthread_kill_other_threads_np()并沒有通過pthread_cancel()來終止線程,而是直接向管理線程發(fā)"進(jìn)程退出"信號(hào),使所有其他線程都結(jié)束運(yùn)行,而不經(jīng)過Cancel動(dòng)作,當(dāng)然也不會(huì)執(zhí)行退出回調(diào)函數(shù)。盡管LinuxThreads的實(shí)驗(yàn)結(jié)果與文檔說明相同,但代碼實(shí)現(xiàn)中卻是用的__pthread_sig_cancel信號(hào)來kill線程,應(yīng)該效果與執(zhí)行pthread_cancel()是一樣的,其中原因目前還不清楚。 6. TSD 6.1. TSD概念 在單線程程序中,我們經(jīng)常要用到"全局變量"以實(shí)現(xiàn)多個(gè)函數(shù)間共享數(shù)據(jù)。在多線程環(huán)境下,由于數(shù)據(jù)空間是共享的,因此全局變量也為所有線程所共有。但有時(shí)應(yīng)用程序設(shè)計(jì)中有必要提供線程私有的全局變量,僅在某個(gè)線程中有效,但卻可以跨多個(gè)函數(shù)訪問,比如程序可能需要每個(gè)線程維護(hù)一個(gè)鏈表,而使用相同的函數(shù)操作,最簡(jiǎn)單的辦法就是使用同名而不同變量地址的線程相關(guān)數(shù)據(jù)結(jié)構(gòu)。這樣的數(shù)據(jù)結(jié)構(gòu)可以由Posix線程庫(kù)維護(hù),稱為線程私有數(shù)據(jù)(Thread-specific Data,或TSD)。 6.2. 創(chuàng)建與注銷 Posix定義了兩個(gè)API分別用來創(chuàng)建和注銷TSD:
也就是說,數(shù)據(jù)存放與一個(gè)32×32的稀疏矩陣中。同樣,訪問的時(shí)候也由key值經(jīng)過類似計(jì)算得到數(shù)據(jù)所在位置索引,再取出其中內(nèi)容返回。 6.4. 使用范例
給例程創(chuàng)建兩個(gè)線程分別設(shè)置同一個(gè)線程私有數(shù)據(jù)為自己的線程ID,為了檢驗(yàn)其私有性,程序錯(cuò)開了兩個(gè)線程私有數(shù)據(jù)的寫入和讀出的時(shí)間,從程序運(yùn)行結(jié)果可以看出,兩個(gè)線程對(duì)TSD的修改互不干擾。同時(shí),當(dāng)線程退出時(shí),清理函數(shù)會(huì)自動(dòng)執(zhí)行,參數(shù)為tid。 7. 線程同步 7.1. 互斥鎖(互斥信號(hào)量) Pthread的互斥鎖與vxworks的互斥信號(hào)量類似,都是用于互斥保護(hù)。 需要注意的是,linux有取消點(diǎn)的概念,即任務(wù)在運(yùn)行時(shí)可以被取消。如果使用了互斥鎖,可能會(huì)造成為解鎖就被取消。為了解決這一問題,linux提供了可以在取消上加回調(diào)函數(shù)的功能: pthread_cleanup_push() pthread_cleanup_pop() 兩個(gè)必須成對(duì)出現(xiàn)如果線程運(yùn)行到兩個(gè)函數(shù)之間被取消時(shí),push注冊(cè)的函數(shù)會(huì)被調(diào)用。 例程: 如果沒有push與pop兩行,則tid1被殺死后會(huì)導(dǎo)致tid2無法獲取到互斥鎖。
7.2. 條件變量(同步信號(hào)量) Pthread的條件變量與vxworks中同步信號(hào)量類似,都是用于任務(wù)同步。區(qū)別是條件變量的操作不是原子操作,需要借助互斥鎖保證其原子性。7.3. 信號(hào)燈(計(jì)數(shù)信號(hào)量) Pthread的信號(hào)燈與vxworks中計(jì)數(shù)信號(hào)量信號(hào)量類似,都是用于表示資源是否可用。 |
|