線程退出前可能有一些清理工做,可是這部分代碼又不會放到線程主體部分,就須要掛接一個或者幾個線程「清潔工」來作這部分事情。須要這對兄弟:less
#include<pthread.h> void pthread_cleanup_push(void (*rtn)(void *), void *arg); void pthread_cleanup_pop(int execute);
顯然pthread_cleanup_push() 是掛接 清理函數的,它的返回值類型爲 void,有兩個入口參數,第一個參數是清理函數函數指針,第二個參數是傳給清理函數的 typeless pointer 。函數
另外一個兄弟 pthread_cleanup_pop() 是來觸發清理函數的,是按照相反的順序來觸發清理函數的。而若是它的入口參數 execute 爲0值,則對應的清理函數並無真正的執行。測試
例以下面這個例子:spa
1 /**************************************************************** 2 # File Name: thread_cleanup3.c 3 # Author : lintex9527 4 # E-Mail : lintex9527@yeah.net 5 # Created Time: Sat 22 Aug 2015 03:25:09 PM HKT 6 # Purpose : 測試清理函數的觸發順序,以及執行與否。 7 # Outline : 8 # Usage : 9 # -------------------------------------------------- 10 # Result : 11 # -------------------------------------------------- 12 *****************************************************************/ 13 #include <stdio.h> 14 #include <stdlib.h> 15 #include <pthread.h> 16 17 /* 線程傳遞給 清理函數 的參數結構體 */ 18 struct argtype{ 19 int a,b; 20 int result; 21 }; 22 23 void print_argtype(const char *str, struct argtype *p) 24 { 25 printf("%s\n", str); 26 printf(" a = %d, b = %d\n", p->a, p->b); 27 printf(" result = %d\n", p->result); 28 } 29 30 /* for thread 1 */ 31 struct argtype entity1 = { 32 .a = 50, 33 .b = 5, 34 .result = 11 35 }; 36 37 /* 如下是3個清理函數 */ 38 void cleanup_add(void *arg) 39 { 40 struct argtype *p = (struct argtype *)arg; 41 p->result = p->a + p->b; 42 print_argtype("cleanup [add]", p); 43 //pthread_exit((void *)p->result); 44 } 45 46 void cleanup_minus(void *arg) 47 { 48 struct argtype *p = (struct argtype *)arg; 49 p->result = p->a - p->b; 50 print_argtype("cleanup [minus]", p); 51 //pthread_exit((void *)p->result); 52 } 53 54 55 void cleanup_times(void *arg) 56 { 57 struct argtype *p = (struct argtype *)arg; 58 p->result = p->a * p->b; 59 print_argtype("cleanup [times]", p); 60 //pthread_exit((void *)p->result); 61 } 62 63 /* 子線程1函數,臨時地改變了entity1結構體中成員值,檢查清理函數執行點 */ 64 void* thr1_fun(void *arg) 65 { 66 printf("Now thread1 [%lu] start:\n", pthread_self()); 67 68 pthread_cleanup_push(cleanup_times, (void *)&entity1); // cleanup_times 69 entity1.a = 20; 70 entity1.b = 2; 71 pthread_cleanup_push(cleanup_minus, (void *)&entity1); // cleanup_minus 72 pthread_cleanup_push(cleanup_add, (void *)&entity1); // cleanup_add 73 pthread_cleanup_pop(3); // cleanup_add 74 75 entity1.a = 30; 76 entity1.b = 3; 77 pthread_cleanup_pop(1); // cleanup_minus 78 79 entity1.a = 40; 80 entity1.b = 4; 81 pthread_cleanup_pop(1); // cleanup_times 82 83 entity1.a = 80; 84 entity1.b = 8; 85 pthread_exit((void *)entity1.result); 86 } 87 88 89 int main(void) 90 { 91 int err; 92 pthread_t tid1; 93 void *tret; 94 95 err = pthread_create(&tid1, NULL, thr1_fun, NULL); 96 err = pthread_join(tid1, &tret); 97 if (err != 0) 98 { 99 perror("pthread_join"); 100 return -1; 101 } 102 103 printf("In main get result [%d] from thread %lu\n", tret, tid1); 104 print_argtype("main:", &entity1); 105 106 return 0; 107 }
執行結果:.net
$ ./thread_cleanup3.exe Now thread1 [140090204903168] start: cleanup [add] a = 20, b = 2 result = 22 cleanup [minus] a = 30, b = 3 result = 27 cleanup [times] a = 40, b = 4 result = 160 In main get result [160] from thread 140090204903168 main: a = 80, b = 8 result = 160
在這個例子中,我把 pthread_cleanup_pop(int execute) 中的 execute 都設定爲非零值,測試3個清理函數的調用順序,線程
註冊的順序是: cleanup_times --> cleanup_minus --> cleanup_add指針
調用的順序是: cleanup_add --> cleanup_minus --> cleanup_timescode
的的確確是按照相反的順序調用的。blog
爲了測試每個清理函數的執行點,我在每個pthread_cleanup_pop() 以前都修改了 結構體 entity1 的域 a,b。通過比對發現每個 pthread_cleanup_push() 和 pthread_cleanup_pop() 造成一個 pairs,由於它們是基於宏實現的,pthread_cleanup_push() 中包含了一個「{」,而 pthread_cleanup_pop() 中包含了一個「}」 和前面的對應,所以它們必須成對的出現,不然代碼通不過編譯。通過檢查和對比,發現每個 pairs 雖然在代碼形式上互相嵌套,可是它們的執行沒有互相嵌套。即在執行最外面的 cleanup_times() 並無遞歸調用 cleanup_minus() 繼而遞歸調用 cleanup_times()。遞歸
所以在處理最外面的 cleanup_times() 時屏蔽了從 pthread_cleanup_push(cleanup_minus, xxx) 到 pthread_cleanupo_pop(yyy) (與 cleanup_minus 對應的) 部分的代碼。
而在處理 cleanup_minus() 時屏蔽了從 pthread_cleanup_push(cleanup_add, xxx) 到 pthread_cleanup_pop(yyy) (與 cleanup_add 對應的) 部分的代碼。
由於 pop 順序和 push 順序是相反的,那麼從第一個 pop 的順序開始執行: cleanup_add --> cleanup_minus --> cleanup_times.
可是每一次執行 cleanup_xxx 的參數爲何會不同的呢?是從哪裏開始變化的呢?
是從線程函數入口上到下,一直到 pthread_cleanup_pop() 部分的參數對當前的 cleanup_xxx() 函數有效。在當前 pthread_cleanup_pop() 下面的語句是對後面一個 pop() 函數起做用的。
以下面這張圖:
左邊的指示線條表徵的是每個 push 入棧的清理函數可訪問的資源區;
右邊的雙箭頭線表徵的是 push / pop 對子,雖然在代碼形式上有嵌套,可是在函數執行上並不會嵌套執行。
根據分析,
entity1.a , entity1.b 傳遞給 cleanup_add() 函數的值是 20 , 2;
entity1.a , entity1.b 傳遞給 cleanup_minus() 函數的值是 30, 3;
entity1.a , entity1.b 傳遞給 cleanup_times() 函數的值是 40, 4;
而最終在 main thread 中能夠訪問到的 entity1.a, entity1.b 的值是 80 , 8 。那個時候已經沒有 清理函數 cleanup_xxx() 去訪問 entity1 結構體了。
另外,我本來在清理函數內部添加了 pthread_exit() 函數,這會出現什麼狀況呢?好比取消 cleanup_times() 函數裏 pthread_exit() 以前的註釋,編譯運行結果以下:
$ ./thread_cleanup3.exe Now thread1 [140415830189824] start: now cleanup_add. cleanup [add] a = 20, b = 2 result = 22 now cleanup_minus. cleanup [minus] a = 30, b = 3 result = 27 now cleanup_times. cleanup [times] a = 40, b = 4 result = 160 In main get result [160] from thread 140415830189824 main: a = 40, b = 4 result = 160
對比以前,發如今 main thread 中的 a,b 值是40, 4 ,這和子線程退出點有關,子線程沒有走到下面這一步:
entity1.a = 40; entity1.b = 4; printf("now cleanup_times.\n"); pthread_cleanup_pop(1); // cleanup_times
-------------------------------------------------------------------// 下面沒有執行
entity1.a = 80; entity1.b = 8; printf("thread 1 is exit...\n"); pthread_exit((void *)entity1.result);
說明提早使用 pthread_exit() 那麼各個函數訪問的資源就更受限。
可是在2個及以上的清理函數中添加 pthread_exit() ,會致使線程不斷地調用 清理函數,進入死機狀態。
總結就是不要在清理函數中添加 pthread_exit() 。