線程終止

POSIX線程終止相關函數

//頭文件
#include <pthread.h>
//API函數
int
pthread_join(pthread_t thread, void **value_ptr); void pthread_exit(void *retval); int pthread_cancel(pthread_t thread); void pthread_cleanup_push(void (*routine)(void*), void *arg); void pthread_cleanup_pop(int execute);

線程終止方式

  單個線程能夠經過3種方式退出,能夠在不終止整個進程的狀況下,中止線程的控制流。數據結構

(1)線程能夠直接從啓動例程(也就是線程函數)中返回,即執行return語句,返回值是線程的退出碼。異步

(2)線程能夠被同一進程中的其餘線程取消。即其餘線程調用pthread_cancel()函數。ide

(3)線程函數自己調用pthread_exit()。函數返回線程退出後傳出來的retval指針。函數

【說明】spa

1. pthread_exit()函數的參數retval是一個無類型指針,這與pthread_create函數中傳給啓動例程的單個參數相似。進程中的其餘線程能夠經過調用pthread_join函數來訪問到這個指針。線程

2. 調用pthread_join()函數將一直阻塞,直到指定的線程(參數thread)終止,終止方式是上面所描述的3種方式。指針

(1) 若是線程簡單地從啓動例程中返回,即執行return語句,pthread_join函數的參數value_ptr就包含返回碼。code

(2) 若是線程被其餘線程取消,pthread_join函數的參數value_ptr指向的內存單元就被設置爲PTHREAD_CANCELED。blog

(3) 若是線程是調用pthread_exit()函數退出的,pthread_join函數的參數value_ptr將能獲取到pthread_exit()函數返回的retval指針。進程

3. 若是對線程的返回值不感興趣,能夠將pthread_exit函數的retval置爲NULL,這種狀況下,pthread_join()函數仍能夠等待指定的線程終止,但並不會獲取到線程的終止狀態。

實例1:獲取已終止的線程的退出碼。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 
 5 void* thr_fn1(void *arg)  6 {  7     printf("thread1 returning***\n");  8     return (void*)1;  9 } 10 
11 void* thr_fn2(void *arg) 12 { 13     printf("thread2 exiting###\n"); 14     pthread_exit((void*)2); 15 } 16 
17 int main(int argc, char *argv[]) 18 { 19     int err; 20  pthread_t tid1, tid2; 21     void *tret; 22     
23     if((err=pthread_create(&tid1, NULL, thr_fn1, NULL) != 0)){ 24         printf("Error: can`t create thread1!\n"); 25  } 26     if((err=pthread_create(&tid2, NULL, thr_fn2, NULL) != 0)){ 27         printf("Error: can`t create thread2!\n"); 28  } 29     
30     if((err=pthread_join(tid1, &tret)) != 0){ 31         printf("Error: can`t join with thread1!\n"); 32  } 33     printf("thread1 exit code: %d\n", (int*)tret); 34     if((err=pthread_join(tid2, &tret)) != 0){ 35         printf("Error: can`t join with thread2!\n"); 36  } 37     printf("thread2 exit code: %d\n", (int*)tret); 38     
39     return 0; 40 }
View Code

## 運行結果:

thread1 returning***
thread2 exiting###
thread1 exit code: 1
thread2 exit code: 2

【分析】從運行結果能夠看出,當一個線程經過調用pthread_exit退出或者簡單地從啓動例程中返回(return語句),進程中的其餘線程能夠經過調用pthread_join函數得到該進程的退出狀態。

【說明】pthread_create和pthread_exit函數的無類型指針參數能夠傳遞的值不止一個,這個指針能夠是包含複雜信息的結構的地址,可是注意的是,這個結構指針指向的內存空間在調用者(線程函數)完成調用之後仍然是有效的。例如,在調用線程的棧區上分配了該結構,那麼其餘線程在使用這個結構時內存內容可能已經改變了。又如,線程在本身的棧區上分配了一個結構,而後把指向這個結構的指針傳給pthread_exit,那麼調用pthread_join的線程試圖使用該結構時,這個棧區有可能已經被撤銷,這塊內存也已另做他用。

實例2:用局部(自動)變量(在棧區上分配的變量)做爲pthread_exit的參數時出現的問題。

#include <stdio.h> #include <unistd.h> #include <pthread.h> typedef struct foo{ int a,b,c,d; }FOO; void printfoo(const char *s, const FOO *fp) { printf("%s", s); printf(" structure at %p\n", 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) { FOO foo={1,2,3,4}; printf("thread 1: thread_id=%lu\n", pthread_self()); printfoo("thread 1:\n", &foo); printf("****** thread 1 exiting\n"); pthread_exit((void*)&foo); } void* thr_fn2(void *arg) { printf("thread 2: thread_id=%lu\n", pthread_self()); printf("###### thread 2 exiting\n"); pthread_exit((void*)2); } int main(int argc, char *argv[]) { int err; pthread_t tid1,tid2; FOO *fp; printf("Parent starting the first thread\n"); if((err=pthread_create(&tid1, NULL, thr_fn1, NULL) != 0)){ printf("Error: can`t create thread1!\n"); } if((err=pthread_join(tid1, (void*)&fp)) != 0){ printf("Error: can`t join with thread1!\n"); } sleep(1); printf("\nParent starting the second thread\n"); if((err=pthread_create(&tid2, NULL, thr_fn2, NULL) != 0)){ printf("Error: can`t create thread2!\n"); } sleep(1); printfoo("\nParent thread:\n", fp); return 0; }
View Code

## 運行結果:

Parent starting the first thread
thread 1: thread_id=140128023041792
thread 1:
structure at 0x7f7219094f00
foo.a = 1
foo.b = 2
foo.c = 3
foo.d = 4
****** thread 1 exiting

Parent starting the second thread
thread 2: thread_id=140128023041792
###### thread 2 exiting

Parent thread:
structure at 0x7f7219094f00
foo.a = 420042496
foo.b = 32626
foo.c = 1
foo.d = 0

【分析】從運行結果能夠看出,當主線程訪問局部結構時,結構的內容(在線程tid1的棧上分配的)已經發生改變了。即主線程試圖訪問已退出的tid1線程傳給它的結構時,因爲該結構是在線程tid1的棧區上定義的,當線程退出時,棧區的內存空間也隨之釋放掉了,因此讀取到的內容是隨機值。爲了解決這個問題,可使用動態內存分配(malloc)或者使用全局結構。

 線程取消機制

  在默認狀況下,pthread_cancel()函數會使得thread標識的線程的行爲表現爲如同調用了參數爲PTHREAD_CANCELED的pthread_exit()函數,即pthread_exit(PTHREAD_CANCELED)。可是,線程能夠選擇忽略取消或者控制如何被取消。【注意】pthread_cancel函數並不等待線程終止,它僅僅是提出請求。

實例3:線程取消的使用。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 
 5 int done = 0;  6 int cnt = 0;  7 
 8 void* thr_fn(void *arg)  9 { 10     //printf("new thread start\n"); //線程取消點
11     while(!done){ 12         cnt++; 13         if(cnt == 10) 14             pthread_testcancel(); //本身設置一個線程取消點
15  } 16     return ((void*)1); 17 } 18 
19 int main(int argc, char *argv[]) 20 { 21     int err; 22  pthread_t tid; 23     void *tret; 24     
25     if(0 != (err=pthread_create(&tid, NULL, thr_fn, NULL))){ 26         printf("Error: can`t create thread\n"); 27         return -1; 28  } 29  pthread_cancel(tid); 30     if(0 != (err=pthread_join(tid, &tret))){ 31         printf("Error: can`t join with thread\n"); 32         return -2; 33  } 34     printf("thread exit code: %d\n", (int*)tret); 35     printf("cnt = %d\n", cnt); 36     
37     return 0; 38 }
View Code

 ## 運行結果:

thread exit code: -1
cnt = 10

【分析】在主線程中調用了pthread_cancel(tid),在線程的啓動例程中,當cnt==10時,調用了pthread_testcancel()函數,這個函數是表示設置一個函數取消點。當線程運行到取消點的時候,線程就會終止。線程退出時的狀態碼爲-1,說明了線程的退出是非正常退出的,而正常退出是的狀態碼應該是1。

【說明】線程在收到pthread_cancel的取消請求後,可能會忽略、當即取消線程或者運行至取消點再取消線程。系統默認狀況下,收到取消請求後,線程會繼續運行,直到遇到下一個取消點處終止線程。

取消點:取消點是線程檢查它是否被取消的一個點,posix保證在一些函數中會自帶取消點,如sleep,accept,write,read,printf等,當執行上述函數時,自動觸發線程取消點,使線程終止。

【擴展】實際上,線程是否取消除了與取消點有關外,還和線程的取消狀態有關。取消狀態分爲:PTHREAD_CANCEL_ENABLE(可取消狀態,這是系統默認的線程取消狀態);PTHREAD_CANCEL_DISABLE(不可取消狀態)。當線程的取消狀態是PTHREAD_CANCEL_DISABLE時,即便線程收到取消請求在取消點也不會取消線程,直到可取消狀態變動爲PTHREAD_CANCEL_ENABLE時,線程纔會在下一個取消點取消線程。

//設置線程取消點函數
void pthread_testcancel(void);

//
修改線程的取消狀態函數 int pthread_setcancelstate(int state, int *oldstate);

【參數說明】
state:設置新狀態值。
oldstate:存放原先的取消狀態。
【函數說明】該函數會在函數內部設置一個取消點,調用該函數時,若是收到一個取消請求,且取消狀態是可取消的,就會當即將線程取消。若是取消狀態爲不可取消,且沒有取消請求,就不會取消,直到二者條件都知足時纔會取消函數。

  在線程的屬性中還有一個屬性與線程的取消有關,即它的取消類型,以前咱們所說的取消屬於推遲取消,即在調用pthread_cancel函數後,須要等到線程運行至一個取消點時,線程纔會被取消而終止線程。

可是,還有一種取類型爲異步取消,即當調用pthread_cancel後,線程就會被當即取消,而不用等到線程運行至取消點時再取消線程,取消類型同取消狀態同樣能夠修改。

//修改線程的取消類型函數 int pthread_setcanceltype(int type, int *oldtype);
【參數說明】
type:設置新的取消類型。
oldtype:存放原先的取消類型。
【函數說明】取消類型有:PTHREAD_CANCEL_DEFERRED、PTHREAD_CANCEL_ASYNCHRONOUS。
PTHREAD_CANCEL_DEFERRED:線程接收到取消請求後,直到運行至"取消點"後才取消線程。

PTHREAD_CANCEL_ASYNCHRONOUS:線程接收到取消請求後,當即取消線程。

<說明>線程的「取消狀態」和「取消類型」存在於任意一個新建線程中,包括主線程,默認設置是PTHREAD_CANCEL_ENABLE 和 PTHREAD_CANCEL_DEFERRED。

 線程清理處理程序

  線程能夠安排它退出時須要調用的函數,這與進程在退出時能夠用atexit函數安排退出是相似的。這樣的函數被稱爲線程處理清理程序(thread cleanup handler)。一個線程能夠創建多個清理處理程序。處理程序記錄在棧中,也就是說它們的執行順序與它們註冊時相反。

//註冊線程清理處理程序
void pthread_cleanup_push(void (*rtn)(void*), void *arg);
【參數】
rtn:線程退出時被調用的清理函數。
arg:傳入給rtn的參數。
//解除線程清理處理程序 void pthread_cleanup_pop(int execute);

【說明】當線程執行如下動做時,清理函數rtn是由phtread_cleanup_push函數調度的,調用時只傳入一個參數arg。

  • 線程函數調用pthread_exit時;
  • 響應取消線程請求時;
  • 用非零execute參數調用pthread_cleanup_pop時。

<注意> 若是pthread_cleanup_pop的execute參數若是設置爲0,清理函數rtn將不被調用,也就是說,線程函數執行pthread_cleanup_pop(0)時,在phtread_cleanup_push中註冊的清理函數rtn將不被執行,可是

pthread_cleanup_pop函數仍將刪除上次在phtread_cleanup_push函數中註冊的清理處理程序(或函數)。

【擴展】這兩個函數有一個限制,由於它們能夠實現爲宏,pthread_cleanup_push()與pthread_cleanup_pop()必須成對的出如今線程函數相同的做用域中。

pthread_cleanup_push的宏定義能夠包含字符 { ,這種狀況下,在與pthread_cleanup_pop的宏定義中要有對應的匹配字符 } 。示例以下:

#define pthread_cleanup_push(rtn,arg) { \
struct _pthread_handler_rec __cleanup_handler, **__head; \ __cleanup_handler.rtn = rtn; \ __cleanup_handler.arg = arg; \ (void) pthread_getspecific(_pthread_handler_key, &__head); \ __cleanup_handler.next = *__head; \ *__head = &__cleanup_handler; #define pthread_cleanup_pop(ex) \
*__head = __cleanup_handler.next; \ if (ex) (*__cleanup_handler.rtn)(__cleanup_handler.arg); \ }

  若是pthread_cleanup_pop函數的參數execute設置爲0,清理將不被調用。但無論發生上述哪一種狀況,pthread_cleanup_pop都將刪除上次pthread_cleanup_push調用創建的清理處理程序。示例以下:

pthread_cleanup_push(routine, &arg); ...... pthread_cleanup_pop(0); pthread_exit((void*)1);

<說明>當線程函數執行到pthread_exit函數時,pthread_cleanup_pop函數將解除pthread_cleanup_push函數註冊的清理處理函數routine,可是不會執行routine中的函數體代碼。

實例4:使用線程清理處理程序。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 
 5 void cleanup(void *arg)  6 {  7     printf("cleanup: %s\n", (char*)arg);  8 }  9 
10 void* thr_fn1(void *arg) 11 { 12     printf("thread 1 start\n"); 13     pthread_cleanup_push(cleanup, "thread 1 first handler"); 14     pthread_cleanup_push(cleanup, "thread 1 secend handler"); 15     printf("thread 1 push complete\n"); 16     if(arg) 17         return ((void*)11); 18     pthread_cleanup_pop(0); 19     pthread_cleanup_pop(0); 20     return ((void*)12); 21 } 22 
23 void* thr_fn2(void *arg) 24 { 25     printf("thread 2 start\n"); 26     pthread_cleanup_push(cleanup, "thread 2 first handler"); 27     pthread_cleanup_push(cleanup, "thread 2 secend handler"); 28     printf("thread 2 push complete\n"); 29     if(arg) 30         pthread_exit((void*)21); 31     pthread_cleanup_pop(0); 32     pthread_cleanup_pop(0); 33     pthread_exit((void*)22); 34 } 35 
36 void* thr_fn3(void *arg) 37 { 38     printf("thread 3 start\n"); 39     pthread_cleanup_push(cleanup, "thread 3 first handler"); 40     pthread_cleanup_push(cleanup, "thread 3 secend handler"); 41     printf("thread 3 push complete\n"); 42     if(arg) 43         pthread_exit((void*)31); 44     pthread_cleanup_pop(0); 45     pthread_cleanup_pop(0); 46     pthread_exit((void*)32); 47 } 48 
49 int main(int argc, char *argv[]) 50 { 51     int err; 52  pthread_t tid1, tid2, tid3; 53     void *tret; 54     
55     if(0 != (err = pthread_create(&tid1, NULL, thr_fn1, (void*)1) )){ 56         printf("Error: can`t create thread 1\n"); 57  } 58     if(0 != (err = pthread_create(&tid2, NULL, thr_fn2, (void*)1) )){ 59         printf("Error: can`t create thread 2\n"); 60  } 61     if(0 != (err = pthread_create(&tid3, NULL, thr_fn3, NULL) )){ 62         printf("Error: can`t create thread 3\n"); 63  } 64     
65     if(0 != (err = pthread_join(tid1, &tret))){ 66         printf("Error: can`t join with thread 1\n"); 67  } 68     printf("thread 1 exit code: %d\n", (int*)tret); 69     if(0 != (err = pthread_join(tid2, &tret))){ 70         printf("Error: can`t join with thread 2\n"); 71  } 72     printf("thread 2 exit code: %d\n", (int*)tret); 73     if(0 != (err = pthread_join(tid3, &tret))){ 74         printf("Error: can`t join with thread 3\n"); 75  } 76     printf("thread 3 exit code: %d\n", (int*)tret); 77     
78     return 0; 79 }
View Code

## 運行結果:

thread 1 start
thread 1 push complete
thread 1 exit code: 11
thread 2 start
thread 2 push complete
cleanup: thread 2 secend handler
cleanup: thread 2 first handler
thread 3 start
thread 3 push complete
thread 2 exit code: 21
thread 3 exit code: 32

## 分析:

一、線程1是直接執行return語句終止線程的,即return ((void*)11); 沒有執行到pthread_cleanup_pop(0); 線程就終止了,並無執行在pthread_cleanup_push函數中註冊的清理函數cleanup,由於它不知足註冊的清理函數被調用的那3個條件中的任何一個,因此線程1的退出碼爲11,即thread 1 exit code: 11。

二、線程2是執行到pthread_exit((void*)21); 時線程就終止了,知足已註冊的清理函數被調用的條件。這時將調用在pthread_cleanup_push中註冊的清理函數cleanup。從運行結果中能夠看到,調用順序和註冊順序是相反的,這是由於清理函數是記錄在棧中的,而棧是一種先進後出的數據結構。特別值得注意的是,

pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_exit((void*)22);

在線程2的啓動例程函數體中,上面的3條語句是沒有執行到的,從線程2的退出碼結果爲:thread 2 exit code: 21 能夠證實這一點。

三、線程3是執行到pthread_exit((void*)32); 時線程終止,而且在線程函數體中是執行了兩個pthread_cleanup_pop(0); 語句的,因此pthread_cleanup_pop函數會刪除掉在前面的pthread_cleanup_push中註冊的清理函數cleanup,可是不會執行清理處理函數,線程3的退出碼爲:thread 3 exit code: 32。

綜上所述,能夠得出如下結論:

一、若是線程是經過從它的啓動例程中調用return語句而終止的話,它的清理處理程序就不會被調用。

二、清理處理程序是按照與它們註冊時相反的順序被調用的。

進程和線程原語的比較

  在默認狀態下,線程的終止狀態會保存直到對該線程調用pthread_join。可是若是線程已經被分離,線程的底層存儲資源能夠在線程終止時當即被收回。在線程被分離後,咱們不能用pthread_join函數等待它的終止狀態,由於對分離線程調用pthread_join會產生未定義的行爲。分離線程能夠調用pthread_detach()函數。

//線程分離函數
int pthread_detach(pthread_t thread);【參數】thread:待分離的線程ID值。【返回值】成功,返回0;失敗,返回錯誤碼。
相關文章
相關標籤/搜索