整理 pthread

            POSIX thread        pthread 學習


POSIX線程(POSIX threads),簡稱Pthreads,是線程的POSIX標準。該標準定義了創建和操縱線程的一整套API。在類Unix操作系統(Unix、Linux、Mac OS X等)中,都使用Pthreads作爲操作系統的線程。Windows操作系統也有其移植版pthreads-win32。
頭文件
#include<pthread.h>


編譯鏈接參數
-lpthread




數據類型


pthread_t:線程ID
pthread_attr_t:線程屬性






操縱函數


pthread_create():創建一個線程
pthread_exit():終止當前線程
pthread_cancel():中斷另外一個線程的運行
pthread_join():阻塞當前的線程,直到另外一個線程運行結束
pthread_attr_init():初始化線程的屬性
pthread_attr_setdetachstate():設置脫離狀態的屬性(決定這個線程在終止時是否可以被結合)
pthread_attr_getdetachstate():獲取脫離狀態的屬性
pthread_attr_destroy():刪除線程的屬性
pthread_kill():向線程發送一個信號






pthread_create


函數聲明
int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,(void*)(*start_rtn)(void*),void *arg);


返回值
若線程創建成功,則返回0。若線程創建失敗,則返回出錯編號,並且*thread中的內容是未定義的。
返回成功時:
由tidp指向的內存單元被設置爲新創建線程的線程ID。
attr參數用於指定各種不同的線程屬性。
新創建的線程從start_rtn函數的地址開始運行,
該函數只有一個萬能指針參數arg,如果需要向start_rtn函數傳遞的參數不止一個,那麼需要把這些參數放到一個結構中,然後把這個結構的地址作爲arg的參數傳入。


因爲pthread並非Linux系統的默認庫,而是POSIX線程庫。在Linux中將其作爲一個庫來使用,因此加上 -lpthread(或-pthread)以顯式鏈接該庫。函數在執行錯誤時的錯誤信息將作爲返回值返回,並不修改系統全局變量errno,當然也無法使用perror()打印錯誤信息。








pthread_exit
線程通過調用pthread_exit函數終止執行,就如同進程在結束時調用exit函數一樣。這個函數的作用是,終止調用它的線程並返回一個指向某個對象的指針。
線程中:pthread_exit ("thread all done"); 
線程外:pthread_join( thread1, &pth_join_ret1);
    printf("pthread_join 1 returns: %s\n",(char *)pth_join_ret1);






pthread_cancel
int pthread_cancel(pthread_t thread);
發送終止信號給thread線程,如果成功則返回0,否則爲非0值。發送成功並不意味着thread會終止。
若是在整個程序退出時,要終止各個線程,應該在成功發送 CANCEL 指令後,使用 pthread_join 函數,等待指定的線程已經完全退出以後,再繼續執行;否則,很容易產生 “段錯誤”。


int pthread_setcancelstate(int state, int *oldstate)
設置本線程對Cancel信號的反應,state有兩種值:PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE,分別表示收到信號後設爲CANCLED狀態和忽略CANCEL信號繼續運行;old_state如果不爲 NULL則存入原來的Cancel狀態以便恢復。


int pthread_setcanceltype(int type, int *oldtype)
設置本線程取消動作的執行時機,type由兩種取值:PTHREAD_CANCEL_DEFERRED和 PTHREAD_CANCEL_ASYNCHRONOUS,僅當Cancel狀態爲Enable時有效,分別表示收到信號後繼續運行至下一個取消點再退出和 立即執行取消動作(退出);oldtype如果不爲NULL則存入原來的取消動作類型值。


void pthread_testcancel(void)
是說pthread_testcancel在不包含取消點,但是又需要取消點的地方創建一個取消點,以便在一個沒有包含取消點的執行代碼線程中響應取消請求.
線程取消功能處於啓用狀態且取消狀態設置爲延遲狀態時,pthread_testcancel()函數有效。
如果在取消功能處處於禁用狀態下調用pthread_testcancel(),則該函數不起作用。
請務必僅在線程取消線程操作安全的序列中插入pthread_testcancel()。除通過pthread_testcancel()調用以編程方式建立的取消點意外,pthread標準還指定了幾個取消點。測試退出點,就是測試cancel信號.


取消點:
線程取消的方法是向目標線程發Cancel信號,但如何處理Cancel信號則由目標線程自己決定,或者忽略、或者立即終止、或者繼續運行至Cancelation-point(取消點),由不同的Cancelation狀態決定。


線程接收到CANCEL信號的缺省處理(即pthread_create()創建線程的缺省狀態)是繼續運行至取消點,也就是說設置一個CANCELED狀態,線程繼續運行,只有運行至Cancelation-point的時候纔會退出。


pthreads標準指定了幾個取消點,其中包括:
(1)通過pthread_testcancel調用以編程方式建立線程取消點。 
(2)線程等待pthread_cond_wait或pthread_cond_timewait()中的特定條件。 
(3)被sigwait(2)阻塞的函數 
(4)一些標準的庫調用。通常,這些調用包括線程可基於阻塞的函數。 
  
缺省情況下,將啓用取消功能。有時,您可能希望應用程序禁用取消功能。如果禁用取消功能,則會導致延遲所有的取消請求,直到再次啓用取消請求。
根據POSIX標準,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函數以及read()、write()等會引起阻塞的系統調用都是Cancelation-point,而其他pthread函數都不會引起Cancelation動作。但是pthread_cancel的手冊頁聲稱,由於LinuxThread庫與C庫結合得不好,因而目前C庫函數都不是Cancelation-point;但CANCEL信號會使線程從阻塞的系統調用中退出,並置EINTR錯誤碼,因此可以在需要作爲Cancelation-point的系統調用前後調用pthread_testcancel(),從而達到POSIX標準所要求的目標.
即如下代碼段:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();


注意:
程序設計方面的考慮,如果線程處於無限循環中,且循環體內沒有執行至取消點的必然路徑,則線程無法由外部其他線程的取消請求而終止。因此在這樣的循環體的必經路徑上應該加入pthread_testcancel()調用.






取消類型(Cancellation Type)


我們會發現,通常的說法:某某函數是 Cancellation Points,這種方法是容易令人混淆的。
因爲函數的執行是一個時間過程,而不是一個時間點。其實真正的 Cancellation Points 只是在這些函數中 Cancellation Type 被修改爲 PHREAD_CANCEL_ASYNCHRONOUS 和修改回 PTHREAD_CANCEL_DEFERRED 中間的一段時間。


POSIX的取消類型有兩種,一種是延遲取消(PTHREAD_CANCEL_DEFERRED),這是系統默認的取消類型,即在線程到達取消點之前,不會出現真正的取消;另外一種是異步取消(PHREAD_CANCEL_ASYNCHRONOUS),使用異步取消時,線程可以在任意時間取消。




線程終止的清理工作


Posix的線程終止有兩種情況:正常終止和非正常終止。
線程主動調用pthread_exit()或者從線程函數中return都將使線程正常退出,這是可預見的退出方式;
非正常終止是線程在其他線程的干預下,或者由於自身運行出錯(比如訪問非法地址)而退出,這種退出方式是不可預見的。


不論是可預見的線程終止還是異常終止,都會存在資源釋放的問題,在不考慮因運行出錯而退出的前提下,如何保證線程終止時能順利的釋放掉自己所佔用的資源,特別是鎖資源,就是一個必須考慮解決的問題。
最經常出現的情形是資源獨佔鎖的使用:線程爲了訪問臨界資源而爲其加上鎖,但在訪問過程中被外界取消,如果線程處於響應取消狀態,且採用異步方式響應,或者在打開獨佔鎖以前的運行路徑上存在取消點,則該臨界資源將永遠處於鎖定狀態得不到釋放。外界取消操作是不可預見的,因此的確需要一個機制來簡化用於資源釋放的編程。


在POSIX線程API中提供了一個pthread_cleanup_push()/ pthread_cleanup_pop()函數,
對用於自動釋放資源—從pthread_cleanup_push()的調用點到pthread_cleanup_pop()之間的程序段中的終止動作(包括調用pthread_exit()和取消點終止)都將執行pthread_cleanup_push()所指定的清理函數。


API定義如下:
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() 的調用將在清理函數棧中形成一個函數鏈;
從pthread_cleanup_push的調用點到pthread_cleanup_pop之間的程序段中的終止動作(包括調用pthread_exit()和異常終止,不包括return)
都將執行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);
或者
void cleanup(void *arg)
{    
    pthread_mutex_unlock(&mutex);
}


void* thread0(void* arg)
{    
    pthread_cleanup_push(cleanup, NULL); // thread cleanup handler
    thread_mutex_lock(&mutex);    
    pthread_cond_wait(&cond, &mutex);    
    pthread_mutex_unlock(&mutex);    
    pthread_cleanup_pop(0);    
    pthread_exit(NULL);
}




pthread_join
函數pthread_join用來等待一個線程的結束,線程間同步的操作。
函數定義: int pthread_join(pthread_t thread, void **retval);
描述 :pthread_join()函數,以阻塞的方式等待thread指定的線程結束。當函數返回時,被等待線程的資源被收回。如果線程已經結束,那麼該函數會立即返回。並且thread指定的線程必須是joinable的。
參數 :thread: 線程標識符,即線程ID,標識唯一線程。retval: 用戶定義的指針,用來存儲被等待線程的返回值。
返回值 : 0代表成功。 失敗,返回的則是錯誤號。


linux中的應用
在Linux中,默認情況下是在一個線程被創建後,必須使用此函數對創建的線程進行資源回收,但是可以設置Threads attributes來設置當一個線程結束時,直接回收此線程所佔用的系統資源,詳細資料查看Threads attributes。
其實在Linux中,新建的線程並不是在原先的進程中,而是系統通過一個系統調用clone()。該系統調用copy了一個和原先進程完全一樣的進程,並在這個進程中執行線程函數。不過這個copy過程和fork不一樣。 copy後的進程和原先的進程共享了所有的變量,運行環境。這樣,原先進程中的變量變動在copy後的進程中便能體現出來。
pthread_join的應用
pthread_join使一個線程等待另一個線程結束。
代碼中如果沒有pthread_join主線程會很快結束從而使整個進程結束,從而使創建的線程沒有機會開始執行就結束了。加入pthread_join後,主線程會一直等待直到等待的線程結束自己才結束,使創建的線程有機會執行。
所有線程都有一個線程號,也就是Thread ID。其類型爲pthread_t。通過調用pthread_self()函數可以獲得自身的線程號。




pthread_attr_init
作用是初始化一個線程對象的屬性,需要用pthread_attr_destroy函數對其去除初始化。
函數聲明
int pthread_attr_init(pthread_attr_t *attr);
返回值
返回0,表示函數初始化對象成功。失敗時返回一個錯誤代碼。
參數說明
指向一個線程屬性結構的指針,結構中的元素分別對應着新線程的運行屬性。屬性對象主要包括是否綁定、是否分離、堆棧地址和大小、優先級等。默認屬性爲非綁定、非分離、默認1MB堆棧、與父進程有相同優先級。






pthread_attr_setdetachstate
在任何一個時間點上,線程是可結合的(joinable),或者是分離的(detached)。一個可結合的線程能夠被其他線程收回其資源和殺死;在被其他線程回收之前,它的存儲器資源(如棧)是不釋放的。相反,一個分離的線程是不能被其他線程回收或殺死的,它的存儲器資源在它終止時由系統自動釋放。


線程的分離狀態決定一個線程以什麼樣的方式來終止自己。在默認情況下線程是非分離狀態的,這種情況下,原有的線程等待創建的線程結束。只有當pthread_join()函數返回時,創建的線程纔算終止,才能釋放自己佔用的系統資源。而分離線程不是這樣子的,它沒有被其他的線程所等待,自己運行結束了,線程也就終止了,馬上釋放系統資源。程序員應該根據自己的需要,選擇適當的分離狀態。所以如果我們在創建線程時就知道不需要了解線程的終止狀態,則可以pthread_attr_t結構中的detachstate線程屬性,讓線程以分離狀態啓動。


設置線程分離狀態的函數爲pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二個參數可選爲PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD_CREATE_JOINABLE(非分離線程)。這裏要注意的一點是,如果設置一個線程爲分離線程,而這個線程運行又非常快,它很可能在pthread_create函數返回之前就終止了,它終止以後就可能將線程號和系統資源移交給其他的線程使用,這樣調用pthread_create的線程就得到了錯誤的線程號。要避免這種情況可以採取一定的同步措施,最簡單的方法之一是可以在被創建的線程裏調用pthread_cond_timewait函數,讓這個線程等待一會兒,留出足夠的時間讓函數pthread_create返回。設置一段等待時間,是在多線程編程裏常用的方法。但是注意不要使用諸如wait()之類的函數,它們是使整個進程睡眠,並不能解決線程同步的問題。


創建 detach 線程:
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, THREAD_FUNCTION, arg);


總之爲了在使用 pthread 時避免線程的資源在線程結束時不能得到正確釋放,從而避免產生潛在的內存泄漏問題,在對待線程結束時,要確保該線程處於 detached 狀態,否着就需要調用 pthread_join() 函數來對其進行資源回收。




pthread_attr_destroy是一種函數,功能是銷燬一個目標結構,並且使它在重新初始化之前不能重新使用。。
函數原型
int pthread_attr_destroy(pthread_attr_t *attr);
函數說明
銷燬一個目標結構,並且使它在重新初始化之前不能重新使用。
輸入參數
要刪除的線程屬性結構體指針
返回值
成功返回0
錯誤返回錯誤代碼




函數原型
int pthread_kill(pthread_t thread, int sig);
作用
向某個線程傳遞一個信號
參數
pthread_t thread:線程號
int sig:信號
注意
請在創建的線程中使用signal(SIGKILL,sig_handler)處理信號,如果你給一個線程發送了SIGQUIT,但線程卻沒有實現signal處理函數,則整個進程退出。




同步函數


用於 mutex 和條件變量
pthread_mutex_init() 初始化互斥鎖
pthread_mutex_destroy() 刪除互斥鎖
pthread_mutex_lock():佔有互斥鎖(阻塞操作)
pthread_mutex_trylock():試圖佔有互斥鎖(不阻塞操作)。即,當互斥鎖空閒時,將佔有該鎖;否則,立即返回。
pthread_mutex_unlock(): 釋放互斥鎖
pthread_cond_init():初始化條件變量
pthread_cond_destroy():銷燬條件變量
pthread_cond_signal(): 喚醒第一個調用pthread_cond_wait()而進入睡眠的線程
pthread_cond_wait(): 等待條件變量的特殊條件發生
Thread-local storage(或者以Pthreads術語,稱作線程特有數據):
pthread_key_create(): 分配用於標識進程中線程特定數據的鍵
pthread_setspecific(): 爲指定線程特定數據鍵設置線程特定綁定
pthread_getspecific(): 獲取調用線程的鍵綁定,並將該綁定存儲在 value 指向的位置中
pthread_key_delete(): 銷燬現有線程特定數據鍵
pthread_attr_getschedparam();獲取線程優先級
pthread_attr_setschedparam();設置線程優先級








工具函數
pthread_equal(): 對兩個線程的線程標識號進行比較
pthread_detach(): 分離線程
pthread_self(): 查詢線程自身線程標識號


pthread_detach
pthread_detach,創建一個線程默認的狀態是joinable。
創建一個線程默認的狀態是joinable, 如果一個線程結束運行但沒有被join,則它的狀態類似於進程中的Zombie Process,即還有一部分資源沒有被回收(退出狀態碼),所以創建線程者應該pthread_join來等待線程運行結束,並可得到線程的退出代碼,回收其資源(類似於wait,waitpid)


但是調用pthread_join(pthread_id)後,如果該線程沒有運行結束,調用者會被阻塞,在有些情況下我們並不希望如此,比如在Web服務器中當主線程爲每個新來的鏈接創建一個子線程進行處理的時候,主線程並不希望因爲調用pthread_join而阻塞(因爲還要繼續處理之後到來的鏈接),這時可以在子線程中加入代碼 pthread_detach(pthread_self()) 或者父線程調用 pthread_detach(thread_id)(非阻塞,可立即返回)這將該子線程的狀態設置爲detached,則該線程運行結束後會自動釋放所有資源。

發佈了22 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章