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值,则对应的清理函数并没有真正的执行。
例如下面这个例子:
/****************************************************************
# File Name: thread_cleanup3.c
# Author : lintex9527
# E-Mail : lintex9527@yeah.net
# Created Time: Sat 22 Aug 2015 03:25:09 PM HKT
# Purpose : 测试清理函数的触发顺序,以及执行与否。
# Outline :
# Usage :
# --------------------------------------------------
# Result :
# --------------------------------------------------
*****************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h> /* 线程传递给 清理函数 的参数结构体 */
struct argtype{
int a,b;
int result;
}; void print_argtype(const char *str, struct argtype *p)
{
printf("%s\n", str);
printf(" a = %d, b = %d\n", p->a, p->b);
printf(" result = %d\n", p->result);
} /* for thread 1 */
struct argtype entity1 = {
.a = ,
.b = ,
.result =
}; /* 以下是3个清理函数 */
void cleanup_add(void *arg)
{
struct argtype *p = (struct argtype *)arg;
p->result = p->a + p->b;
print_argtype("cleanup [add]", p);
//pthread_exit((void *)p->result);
} void cleanup_minus(void *arg)
{
struct argtype *p = (struct argtype *)arg;
p->result = p->a - p->b;
print_argtype("cleanup [minus]", p);
//pthread_exit((void *)p->result);
} void cleanup_times(void *arg)
{
struct argtype *p = (struct argtype *)arg;
p->result = p->a * p->b;
print_argtype("cleanup [times]", p);
//pthread_exit((void *)p->result);
} /* 子线程1函数,临时地改变了entity1结构体中成员值,检查清理函数执行点 */
void* thr1_fun(void *arg)
{
printf("Now thread1 [%lu] start:\n", pthread_self()); pthread_cleanup_push(cleanup_times, (void *)&entity1); // cleanup_times
entity1.a = ;
entity1.b = ;
pthread_cleanup_push(cleanup_minus, (void *)&entity1); // cleanup_minus
pthread_cleanup_push(cleanup_add, (void *)&entity1); // cleanup_add
pthread_cleanup_pop(); // cleanup_add entity1.a = ;
entity1.b = ;
pthread_cleanup_pop(); // cleanup_minus entity1.a = ;
entity1.b = ;
pthread_cleanup_pop(); // cleanup_times entity1.a = ;
entity1.b = ;
pthread_exit((void *)entity1.result);
} int main(void)
{
int err;
pthread_t tid1;
void *tret; err = pthread_create(&tid1, NULL, thr1_fun, NULL);
err = pthread_join(tid1, &tret);
if (err != )
{
perror("pthread_join");
return -;
} printf("In main get result [%d] from thread %lu\n", tret, tid1);
print_argtype("main:", &entity1); return ;
}
执行结果:
$ ./thread_cleanup3.exe
Now thread1 [] start:
cleanup [add]
a = , b =
result =
cleanup [minus]
a = , b =
result =
cleanup [times]
a = , b =
result =
In main get result [] from thread
main:
a = , b =
result =
顺序测试
在这个例子中,我把 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 [] start:
now cleanup_add.
cleanup [add]
a = , b =
result =
now cleanup_minus.
cleanup [minus]
a = , b =
result =
now cleanup_times.
cleanup [times]
a = , b =
result =
In main get result [] from thread
main:
a = , b =
result =
对比之前,发现在 main thread 中的 a,b 值是40, 4 ,这和子线程退出点有关,子线程没有走到下面这一步:
entity1.a = ;
entity1.b = ;
printf("now cleanup_times.\n");
pthread_cleanup_pop(); // cleanup_times
-------------------------------------------------------------------// 下面没有执行
entity1.a = ;
entity1.b = ;
printf("thread 1 is exit...\n");
pthread_exit((void *)entity1.result);
说明提前使用 pthread_exit() 那么各个函数访问的资源就更受限。
但是在2个及以上的清理函数中添加 pthread_exit() ,会导致线程不断地调用 清理函数,进入死机状态。
总结就是不要在清理函数中添加 pthread_exit() 。
Linux 下子线程的 pthread_cleanup_push() 和 pthread_cleanup_pop() 研究的更多相关文章
- Linux 下子线程 exit code 在主线程中的使用
Linux线程函数原型是这样的: void* thread_fun(void* arg) 它的返回值是 空类型指针,入口参数也是 空类型指针.那么线程的 exit code 也应该是 void * 类 ...
- Linux编程---线程
首先说一下线程的概念.事实上就是运行在进程的上下文环境中的一个运行流.普通进程仅仅有一条运行流,可是线程提供了多种运行的路径并行的局面. 同一时候,线程还分为核心级线程和用户级线程.主要差别在属于核内 ...
- pthread_cleanup_push与pthread_cleanup_pop与pthread_cancel与pthread_testcancel
参考: http://blog.csdn.net/zjc156m/article/details/9021343 http://blog.csdn.net/u010027547/article/det ...
- Linux中线程使用详解
线程与进程为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题. 使用多线程的理由之一是和进程相比,它是一种非常"节俭&qu ...
- Linux/UNIX线程(1)
线程(1) 本文将介绍怎样使用多个控制线程在单个进程环境中运行多个任务. 一个进程中的全部线程都能够訪问该进程的组成部件(如文件描写叙述符和内存). 线程包含了表示进程内运行环境必须的信息,当中包含进 ...
- Linux 下线程的理解
2017-04-03 最近深入研究了下Linux线程的问题,发现自己之前一直有些许误解,特记之…… 关于Linux下的线程,各种介绍Linux的书籍都没有深入去解释的,或许真的如书上所述,Linux本 ...
- 【Linux】线程并发拷贝程序
据说大连某211高校的李教授越来越重口.不仅延续要求他所带的每个本科班.都要写一份线程并发拷贝程序的传统,并且还開始规定不能用Java语言写作.导致我之前写的<[Java]线程并发拷贝程序> ...
- pthread_cleanup_push与pthread_cleanup_pop的理解
一.为什么会有pthread_cleanup_push与pthread_cleanup_pop: 一般来说,Posix的线程终止有两种情况:正常终止和非正常终止.线程主动调用pthread_exit( ...
- Linux/Unix 线程同步技术之互斥量(1)
众所周知,互斥量(mutex)是同步线程对共享资源访问的技术,用来防止下面这种情况:线程A试图访问某个共享资源时,线程B正在对其进行修改,从而造成资源状态不一致.与之相关的一个术语临界区(critic ...
随机推荐
- Lucene.net站内搜索—4、搜索引擎第一版技术储备(简单介绍Log4Net、生产者消费者模式)
目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...
- struts2输入验证
1.方法 ① 基于Annotations的验证 ②基于XML配置的验证 http://blog.csdn.net/furongkang/article/details/692204 ...
- ubuntu15.10_x64 安装 nginx
浏览器到 http://nginx.org/en/download.html 下载 stable version的nginx 如nginx-1.10.1.tar.gz,这是一个nginx源码包,需 ...
- ClockPicker – 时钟风格 Bootstrap 时间选择器
ClockPicker 是国内前端开发者发布的一个时钟样式 Timepicker,可以用于 Bootstrap 和 jQuery.所有主流浏览器都支持,包括 IE9+,支持移动设备,能够在触摸屏设备很 ...
- asp.net中控制反转的理解
对IOC的解释为:“Inversion of control is a common characteristic of frameworks, so saying that these lightw ...
- HTML <b>、 <strong> 、<big>、<small>、<em>、<i>、<sub>和<sup> 标签
HTML <b> 标签 所有浏览器都支持 <b> 标签. 定义和用法 <b> 标签规定粗体文本. 注释:根据 HTML5 规范,在没有其他合适标签更合适时,才应该把 ...
- 通过组策略实现Firefox自动以当前域账号登录MOSS站点---(原创)
忘忧草原创,转发请保留本人的大名,谢谢,如果需要文档的请找我索取 前言 通过组策略实现基于AD的windows验证的sharepoint站点在火狐下自动以当前域账号登录. 操作步骤-在服务器添加策略工 ...
- android Activity runOnUiThread() 方法使用
在android 中我们一般用 Handler 做主线程 和 子线程 之间的通信 . 现在有了一种更为简洁的写法,就是 Activity 里面的 runOnUiThread( Runnable )方法 ...
- Android - 模块添加与编译
Android5.1 Ubuntu14.04 Android系统编译依靠.mk文件 添加google服务 我们自己的ROM里没有google服务 完整的google包里包含google框架和各种服务, ...
- 一起来学习Android自定义控件1
概述 Android已经为我们提供了大量的View供我们使用,但是可能有时候这些组件不能满足我们的需求,这时候就需要自定义控件了.自定义控件对于初学者总是感觉是一种复杂的技术.因为里面涉及到的知识点会 ...