博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux 下子线程的 pthread_cleanup_push() 和 pthread_cleanup_pop() 研究
阅读量:4620 次
发布时间:2019-06-09

本文共 6059 字,大约阅读时间需要 20 分钟。

线程退出前可能有一些清理工作,但是这部分代码又不会放到线程主体部分,就需要挂接一个或者几个线程“清洁工”来做这部分事情。需要这对兄弟:

#include
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 
14 #include
15 #include
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 = 22cleanup [minus]    a = 30, b = 3    result = 27cleanup [times]    a = 40, b = 4    result = 160In main get result [160] from thread 140090204903168main:    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 = 22now cleanup_minus.cleanup [minus]    a = 30, b = 3    result = 27now cleanup_times.cleanup [times]    a = 40, b = 4    result = 160In main get result [160] from thread 140415830189824main:    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() 。

 

转载于:https://www.cnblogs.com/LinTeX9527/p/4750546.html

你可能感兴趣的文章
用vue-cli搭建项目的 Vue-router
查看>>
react hooks学习
查看>>
本地存储 [记录]
查看>>
原型模式
查看>>
C#的一些必备技术
查看>>
【转载】学习顺序:顶级会议 ----> 顶级期刊 ------> 基础教材(博客) / 论文复现...
查看>>
Deep Learnning
查看>>
Css预处理器---Less(二)
查看>>
config windows virtual machine on mac
查看>>
Shell——windows上写完放入linux的时候需要注意的问题
查看>>
Activity总结
查看>>
naze32 rev6 swd 调试接口的引脚定义
查看>>
python3+requests接口自动化session操作
查看>>
qrsub sge
查看>>
thinkphp中array_diff运行无效 Invalid opcode 153/1/8
查看>>
Ubuntu彻底删除/卸载mysql,php,apache
查看>>
noj算法 装载问题 回溯法
查看>>
POJ 2429 GCD & LCM Inverse ★(pollard-ρ && DFS枚举)
查看>>
通过拦截器Interceptor实现Spring MVC中Controller接口访问信息的记录
查看>>
LeetCode: Minimum Depth of Binary Tree
查看>>