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

#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() 研究的更多相关文章

  1. Linux 下子线程 exit code 在主线程中的使用

    Linux线程函数原型是这样的: void* thread_fun(void* arg) 它的返回值是 空类型指针,入口参数也是 空类型指针.那么线程的 exit code 也应该是 void * 类 ...

  2. Linux编程---线程

    首先说一下线程的概念.事实上就是运行在进程的上下文环境中的一个运行流.普通进程仅仅有一条运行流,可是线程提供了多种运行的路径并行的局面. 同一时候,线程还分为核心级线程和用户级线程.主要差别在属于核内 ...

  3. 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 ...

  4. Linux中线程使用详解

    线程与进程为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题. 使用多线程的理由之一是和进程相比,它是一种非常"节俭&qu ...

  5. Linux/UNIX线程(1)

    线程(1) 本文将介绍怎样使用多个控制线程在单个进程环境中运行多个任务. 一个进程中的全部线程都能够訪问该进程的组成部件(如文件描写叙述符和内存). 线程包含了表示进程内运行环境必须的信息,当中包含进 ...

  6. Linux 下线程的理解

    2017-04-03 最近深入研究了下Linux线程的问题,发现自己之前一直有些许误解,特记之…… 关于Linux下的线程,各种介绍Linux的书籍都没有深入去解释的,或许真的如书上所述,Linux本 ...

  7. 【Linux】线程并发拷贝程序

    据说大连某211高校的李教授越来越重口.不仅延续要求他所带的每个本科班.都要写一份线程并发拷贝程序的传统,并且还開始规定不能用Java语言写作.导致我之前写的<[Java]线程并发拷贝程序> ...

  8. pthread_cleanup_push与pthread_cleanup_pop的理解

    一.为什么会有pthread_cleanup_push与pthread_cleanup_pop: 一般来说,Posix的线程终止有两种情况:正常终止和非正常终止.线程主动调用pthread_exit( ...

  9. Linux/Unix 线程同步技术之互斥量(1)

    众所周知,互斥量(mutex)是同步线程对共享资源访问的技术,用来防止下面这种情况:线程A试图访问某个共享资源时,线程B正在对其进行修改,从而造成资源状态不一致.与之相关的一个术语临界区(critic ...

随机推荐

  1. ajax传参到实体类对应字段

    之前看公司的代码,从页面ajax传参数到接口类,接口类用一个实体接收.我一直以为c#会自动识别,赋值到同名的字段. 我曾和朋友讨论过,双方辩论.朋友认为c#没有这种功能,没有这样高级匹配的机制.而我因 ...

  2. Code First :使用Entity. Framework编程(4) ----转发 收藏

    第4章 对关系使用默认规则与配置 在第3章,你已经掌握了默认规则与配置对属性以及其在数据库映射的字段的影响.在本章,我们把焦点放在类之间的关系上面.这包括类在内存如何关联,还有数据库中的外键维持等.你 ...

  3. [deviceone开发]-do_SlideListView的简单示例

    一.简介 利用提供的SlideListVIew实现那种cell可以滑动露出底部按钮的功能 主要组件:do_slidelistview 二.效果图 三.相关讨论 http://bbs.deviceone ...

  4. css引入方式优先级以及不同选择器的优先级区别

    我们都知道css有3种基本设置方式即 1.行内也叫嵌入式 例如: <div style='background:red'></div> 2.内联式,在html文件中用style ...

  5. ArcGIS Server For Linux 10.2.2安装

    1.#  yum install Xvfb#  yum groupinstall "X Window System"# yum install gettext 2./usr/sbi ...

  6. Python统计学技术环境

    http://www.lfd.uci.edu/~gohlke/pythonlibs/#sympy 1.1. Python 1.1.1. NumPy pip install numpy-1.11.0+m ...

  7. Failed to connect to database. Maximum number of conections to instance exceeded

    我们大体都知道ArcSDE的连接数有 48 的限制,很多人也知道这个参数可以修改,并且每种操作系统能支持的最大连接数是不同的. 如果应用报错:超出系统最大连接数 该如何处理? 两种解决办法: 第一,首 ...

  8. Gartner:用自适应安全架构来应对高级定向攻击

    发表于2015-06-24   摘要:当前的防护功能难以应对高级的定向攻击,由于企业系统所受到的是持续攻击,并持续缺乏防御力,面向“应急响应”的特别方式已不再是正确的思维模式,Garnter提出了用自 ...

  9. Autodesk 2013开发者日(DevDays)又要来了 -- 北京(2013年11月7日)和上海(2013年11月11日)

    各位, 一年一度的Autodesk 开发者日(Devdays)开始注册了,抓紧时间前排占座! 注册地址: www.autodesk.com.cn/adndevday2013 今年开发者日的主题:革命性 ...

  10. linux heartbeat v2/v3 的一点资料

    http://linux-ha.org http://linux-ha.org/wiki/Pacemaker Heartbeat2 http://blog.taggesell.de/index.php ...