如果進程中的任一線程調用了exit、_Exit或者_exit,那么整個進程就會終止。與此類似,如果信號的默認動作是終止進程,那么,把該信號發送到線程會終止整個進程。
單個線程可以通過下列三種方式退出,在不終止整個進程的情況下停止它的控制流。
(1)線程只是從啟動例程中返回,返回值是線程的退出碼。
(2)線程可以被同一進程中的其他線程取消。
(3)線程調用pthread_exit。
#include <pthread.h>void pthread_exit(void *rval_ptr);
rval_ptr是一個無類型指針,與傳給啟動例程的單個參數類似。進程中的其他線程可以通過調用pthread_join函數訪問到這個指針。
#include <pthread.h>int pthread_join(pthread_t thread, void **rval_ptr);返回值:若成功則返回0,否則返回錯誤編號
調用線程將一直阻塞,直到指定的線程調用pthread_exit、從啟動例程中返回或者被取消。如果線程只是從它的啟動例程返回,rval_ptr將包含返回碼。如果線程被取消,由rval_ptr指定的內存單元就置為PTHREAD_CANCELED。
可以通過調用pthread_join自動把線程置于分離狀態,這樣資源就可以恢復。如果線程已經處于分離狀態,pthread_join調用就會失敗,返回EINVAL。
如果對線程的返回值并不感興趣,可以把rval_ptr置為NULL。在這種情況下,調用pthread_join函數將等待指定的線程終止,但并不獲取線程的終止狀態。
實例
程序清單11-2說明了如何獲取已終止的線程的退出碼。
程序清單11-2 獲得線程退出狀態
#include "apue.h"#include <pthread.h>void *thr_fn1(void *arg){ PRintf("thread 1 returning/n"); return((void *)1);}void *thr_fn2(void *arg){ printf("thread 2 exiting/n"); pthread_exit((void *)2);}int main(void){ int err; pthread_t tid1, tid2; void *tret; err = pthread_create(&tid1, NULL, thr_fn1, NULL); if(err != 0) err_quit("can't create thread 1: %s/n", strerror(err)); err = pthread_create(&tid2, NULL, thr_fn2, NULL); if(err != 0) err_quit("can't create thread 2: %s/n", strerror(err)); err = pthread_join(tid1, &tret); if(err != 0) err_quit("can't join with thread 1: %s/n", strerror(err)); printf("thread 1 exit code %d/n", (int)tret); err = pthread_join(tid2, &tret); if(err != 0) err_quit("can't join with thread 2: %s/n", strerror(err)); printf("thread 2 exit code %d/n", (int)tret); exit(0);}
運行程序清單11-2中的程序,得到的結果是:
可以看出,當一個線程通過調用pthread_exit退出或者簡單地從啟動例程中返回時,進程中的其他線程可以通過調用pthread_join函數獲得該線程的退出狀態。
pthread_create和pthread_exit函數的無類型指針參數能傳遞的數值可以不止一個,該指針可以傳遞包含更復雜信息的結構的地址,但是注意這個結構所使用的內存在調用者完成調用以后必須仍然是有效的,否則就會出現無效或非法內存訪問。
實例
程序清單11-3中的程序給出了用自動變量(分配在棧上)作為pthread_exit的參數時出現的問題。
程序清單11-3 pthread_exit參數的不正確使用
#include "apue.h"#include <pthread.h>struct foo { int a, b, c, d;};void printfoo(const char *s, const struct foo *fp){ printf(s); printf(" structure at 0x%x/n", (unsigned)fp); printf(" foo.a = %d/n", fp->a); printf(" foo.b = %d/n", fp->b); printf(" foo.c = %d/n", fp->c); printf(" foo.d = %d/n", fp->d);}void *thr_fn1(void *arg){ struct foo foo = {1, 2, 3, 4}; printfoo("thread 1:/n", &foo); pthread_exit((void *)&foo); printfoo("thread 1:/n", &foo);}void *thr_fn2(void *arg){ printf("thread 2: ID is %d/n", pthread_self()); pthread_exit((void *)0);}intmain(void){ int err; pthread_t tid1, tid2; struct foo *fp; err = pthread_create(&tid1, NULL, thr_fn1, NULL); if(err != 0) err_quit("can't create thread 1: %s/n", strerror(err)); err = pthread_join(tid1, (void *)&fp); if(err != 0) err_quit("can't join with thread 1: %s/n", strerror(err)); sleep(1);// printf("parent starting second thread/n"); // err = pthread_create(&tid2, NULL, thr_fn2, NULL);// if(err != 0)// err_quit("cant' create thread 2: %s/n", strerror(err)); sleep(1); printfoo("parent: /n", fp); exit(0);}
運行程序清單11-3中的程序得到:
情況一:把帶注釋的行去掉注釋也編譯進程序中時的運行結果:
情況二:帶注釋的行不包括在程序中時的運行結果:
可以看出,當主線程訪問這個結構時,結構的內容(在線tid1的棧上分配)已經改變。為了解決這個問題,可以使用全局結構,或者用malloc函數分配結構。
例如,若把struct foo foo = {1, 2, 3, 4}; 移到函數外,使其成為全局結構,則可得到如下結果:
線程可以通過調用pthread_cancel函數來請求取消同一進程中的其他線程。
#include <pthread.h>int pthread_cancel(pthread_t tid);返回值:若成功則返回0,否則返回錯誤編號
在默認情況下,pthread_cancel函數會使得由tid標識的線程的行為表現為如同調用了參數為PTHREAD_CANCELED的pthread_exit函數,但是,線程可以選擇忽略取消方式或是控制取消方式。注意,pthread_cancel并不等待線程終止,它僅僅提出請求。
線程可以安排它退出時需要調用的函數,這與進程可以用atexit函數安排進程退出時需要調用的函數是類似的。這樣的函數稱為線程清理處理程序(thread cleanup handler)。線程可以建立多個清理處理程序。處理程序記錄在棧中,也就是說它們的執行順序與它們注冊時的順序相反。
#include <pthread.h>void pthread_cleanup_push(void (*rtn)(void *), void *arg);void pthread_cleanup_pop(int execute);
當線程執行以下動作時調用清理函數(調用參數為arg,清理函數rtn的調用順序是由pthread_cleanup_push函數來安排的):
如果execute參數置為0,清理函數將不被調用。無論哪種情況,pthread_cleanup_pop都將刪除上次pthread_cleanup_push調用建立的清理處理程序。
這些函數有一個限制,由于它們可以實現為宏,所以必須在與線程相同的作用域中以匹配對的形式使用,pthread_cleanup_push的宏定義可以包含字符{,在這種情況下對應的匹配字符}就要在pthread_cleanup_pop定義中出現。
實例
程序清單11-4顯示了如何使用線程清理處理程序。需要把pthread_cleanup_pop調用和pthread_cleanup_push調用匹配起來,否則,程序編譯可能通不過。
程序清單11-4 線程清理處理程序
#include "apue.h"#include <pthread.h>void cleanup(void *arg){ printf("cleanup: %s/n", (char *)arg);}void *thr_fn1(void *arg){ printf("thread 1 start/n"); pthread_cleanup_push(cleanup, "thread 1 first hanlder"); pthread_cleanup_push(cleanup, "thread 1 second handler"); printf("thread 1 push complete/n"); if(arg) return((void *)1); pthread_cleanup_pop(0); pthread_cleanup_pop(0); return((void *)1);}void *thr_fn2(void *arg){ printf("thread 2 start/n"); pthread_cleanup_push(cleanup, "thread 2 first handler"); pthread_cleanup_push(cleanup, "thread 2 second handler"); printf("thread 2 push complete/n"); if (arg) pthread_exit((void *)2); pthread_cleanup_pop(0); pthread_cleanup_pop(0); pthread_exit((void *)2);}intmain(void){ int err; pthread_t tid1, tid2; void *tret; err = pthread_create(&tid1, NULL, thr_fn1, (void *)1); if(err != 0) err_quit("can't create thread 1: %s/n", strerror(err)); err = pthread_create(&tid2, NULL, thr_fn2, (void *)1); if(err != 0) err_quit("can't create thread 2: %s/n", strerror(err)); err = pthread_join(tid1, &tret); if(err != 0) err_quit("can't join with thread 1: %s/n", strerror(err)); printf("thread 1 exit code %d/n", (int)tret); err = pthread_join(tid2, &tret); if(err != 0) err_quit("can't join with thread 2: %s/n", strerror(err)); printf("thread 2 exit code %d/n", (int)tret); exit(0);}
運行程序清單11-4中的程序會得到:
從輸出結果可以看出,兩個線程都正確地啟動和退出了,但是只調用了第二個線程的清理處理程序,所以如果線程是通過從它的啟動例程中返回而終止的話,那么它的清理處理程序就不會被調用,還要注意清理處理程序是按照與它們安裝時相反的順序被調用的。
現在可以開始看出線程函數和進程函數之間的相似之處。表11-1總結了這些相似的函數。
表11-1進程原語和線程原語的比較
在默認情況下,線程的終止狀態會保存一直等到對該線程調用pthread_join。如果線程已經處于分離狀態(參考1http://linux.net527.cn/fuwuqiyingyong/Oracle/2012/0504/46837.html;參考2http://www.CUOXin.com/mydomain/archive/2011/08/14/2138454.htm),線程的底層存儲資源可以在線程終止時立即被收回。
在任何一個時間點上,線程是可結合的(joinable),或者是分離的(detached)。一個可結合的線程能夠被其他線程收回其資源和殺死;在被其他線程回收之前,它的存儲器資源(如棧)是不釋放的。相反,一個分離的線程是不能被其他線程回收或殺死的,它的存儲器資源在它終止時由系統自動釋放。
線程的分離狀態決定一個線程以什么樣的方式來終止自己。線程的默認屬性,即為非分離狀態(即可結合的,joinable,需要回收),這種情況下,原有的線程等待創建的線程結束;只有當pthread_join()函數返回時,創建的線程才算終止,才能釋放自己占用的系統資源。而分離線程不是這樣子的,它沒有被其他的線程所等待,自己運行結束了,線程也就終止了,馬上釋放系統資源。程序員應該根據自己的需要,選擇適當的分離狀態。
當線程被分離時,并不能用pthread_join函數等待它的終止狀態。對分離狀態的線程進行pthread_join的調用會產生失敗,返回EINVAL。pthread_detach調用可以用于使線程進入分離狀態。
#include <pthread.h>int pthread_detach(pthread_t tid);返回值:若成功則返回0,否則返回錯誤編號
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www.apuebook.com/。
新聞熱點
疑難解答