MyException - 我的异常网
当前位置:我的异常网» Linux/Unix » Linux 下子线程的 pthread_cleanup_push() 跟 pthre

Linux 下子线程的 pthread_cleanup_push() 跟 pthread_cleanup_pop() 研究

www.MyException.Cn  网友分享于:2015-08-24  浏览:0次
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() 。

 

文章评论

5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
老程序员的下场
老程序员的下场
程序员必看的十大电影
程序员必看的十大电影
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
 程序员的样子
程序员的样子
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
编程语言是女人
编程语言是女人
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
总结2014中国互联网十大段子
总结2014中国互联网十大段子
中美印日四国程序员比较
中美印日四国程序员比较
如何成为一名黑客
如何成为一名黑客
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
一个程序员的时间管理
一个程序员的时间管理
Java程序员必看电影
Java程序员必看电影
代码女神横空出世
代码女神横空出世
漫画:程序员的工作
漫画:程序员的工作
程序员应该关注的一些事儿
程序员应该关注的一些事儿
每天工作4小时的程序员
每天工作4小时的程序员
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
那些争议最大的编程观点
那些争议最大的编程观点
程序员的鄙视链
程序员的鄙视链
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
鲜为人知的编程真相
鲜为人知的编程真相
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
10个调试和排错的小建议
10个调试和排错的小建议
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
为什么程序员都是夜猫子
为什么程序员都是夜猫子
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
我的丈夫是个程序员
我的丈夫是个程序员
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有