這是一個關於Posix線程編程的專欄。做者在闡明概念的基礎上,將向您詳細講述Posix線程庫API。本文是第四篇將向您講述
線程停止。
1.線程終止方式
通常來講,Posix的線程終止有兩種狀況:正常終止和非正常終止。線程主動調用pthread_exit()或者從線程函數中return都將使線程正常退出,這是可預見的退出方式;非正常終止是線程在其餘線程的干預下,或者因爲自身運行出錯(好比訪問非法地址)而退出,這種退出方式是不可預見的。
2.線程終止時的清理
不管是可預見的線程終止仍是異常終止,都會存在資源釋放的問題,在不考慮因運行出錯而退出的前提下,如何保證線程終止時能順利的釋放掉本身所佔用的資源,特別是鎖資源,就是一個必須考慮解決的問題。
最常常出現的情形是資源獨佔鎖的使用:線程爲了訪問臨界資源而爲其加上鎖,但在訪問過程當中被外界取消,若是線程處於響應取消狀態,且採用異步方式響應,或者在打開獨佔鎖之前的運行路徑上存在取消點,則該臨界資源將永遠處於鎖定狀態得不到釋放。外界取消操做是不可預見的,所以的確須要一個機制來簡化用於資源釋放的編程。
在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()的調用將在清理函數棧中造成一個函數鏈,在執行該函數鏈時按照壓棧的相反順序彈出。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);
}
3.線程終止的同步及其返回值
通常狀況下,進程中各個線程的運行都是相互獨立的,線程的終止並不會通知,也不會影響其餘線程,終止的線程所佔用的資源也並不會隨着線程的終止而獲得釋放。正如進程之間能夠用wait()系統調用來同步終止並釋放資源同樣,線程之間也有相似機制,那就是pthread_join()函數。
void pthread_exit(void *retval) int pthread_join(pthread_t th, void **thread_return)int pthread_detach(pthread_t th)
pthread_join()的調用者將掛起並等待th線程終止,retval是pthread_exit()調用者線程(線程ID爲th)的返回值,若是thread_return不爲NULL,則*thread_return=retval。須要注意的是一個線程僅容許惟一的一個線程使用pthread_join()等待它的終止,而且被等待的線程應該處於可join狀態,即非DETACHED狀態。
若是進程中的某個線程執行了pthread_detach(th),則th線程將處於DETACHED狀態,這使得th線程在結束運行時自行釋放所佔用的內存資源,同時也沒法由pthread_join()同步,pthread_detach()執行以後,對th請求pthread_join()將返回錯誤。
一個可join的線程所佔用的內存僅當有線程對其執行了pthread_join()後纔會釋放,所以爲了不內存泄漏,全部線程的終止,要麼已設爲DETACHED,要麼就須要使用pthread_join()來回收。
4.關於pthread_exit()和return
理論上說,pthread_exit()和線程宿體函數退出的功能是相同的,函數結束時會在內部自動調用pthread_exit()來清理線程相關的資源。但實際上兩者因爲編譯器的處理有很大的不一樣。
在進程主函數(main())中調用pthread_exit(),只會使主函數所在的線程(能夠說是進程的主線程)退出;而若是是return,編譯器將使其調用進程退出的代碼(如_exit()),從而致使進程及其全部線程結束運行。
其次,在線程宿主函數中主動調用return,若是return語句包含在pthread_cleanup_push()/pthread_cleanup_pop()對中,則不會引發清理函數的執行,反而會致使segment fault。