Linux 下子線程的 pthread_cleanup_push() 和 pthread_cleanup_pop() 研究


線程退出前可能有一些清理工作,但是這部分代碼又不會放到線程主體部分,就需要掛接一個或者幾個線程“清潔工”來做這部分事情。需要這對兄弟:

#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值,則對應的清理函數並沒有真正的執行。

例如下面這個例子:

 

  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 }

 

 

執行結果:

$ ./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_times

的的確確是按照相反的順序調用的。

執行點測試

為了測試每一個清理函數的執行點,我在每一個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() 。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM