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

#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. Eclipse安装SVN插件及使用说明

    1.下载Eclipse,如果没有安装的请到这里下载安装:http://eclipse.org/downloads/ ,关于Eclipse的下载安装不再赘述. 2.下载SVN插件subclipse,安装 ...

  2. linux使用rpm重装jdk

    1.卸载jdk #rpm -qa | grep gcj 如果输出没有内容,说明没有jdk,如果输出有内容,要把搜索到的文件卸载掉,命令为: #rpm -e --nodeps [上步操作输出的文件] 然 ...

  3. Date类型 方法

    Date 方法函数 Date.parse():可以获得该日期的毫秒数 var now=Date.parse('May 25,2005')//1085414400000 Date.UTC():也可以获得 ...

  4. angularjs——插值字符串

    一.何为插值字符串? 其实插值字符串的意思就是:拥有字符插值标记的字符串.如: hello,{{ to }}....字符插值标记:相当于我们平时在字符串替换中使用到的占位符.上面的例子中的{{to}} ...

  5. HandlerThread

    一.概念     1.Android中Handler的使用,一般都在UI主线程中执行,因此在Handler接收消息后,处理消息时,不能做一些很耗时的操作,否则将出现ANR错误. 2.HandlerTh ...

  6. 优化MySchool数据库(二)

    优化School数据库(TSQL建库建表建约束) 使用T_sql代码建库.建表.建约束: 建库: Create database HotelManagerSystem on ( ---- 数据文件-- ...

  7. C 运算符优先级列表

  8. Android touch事件处理流程

    前面我们看了key事件的处理流程,相信大家对此已经有了新的认识,这篇文章我打算带领大家来看看稍微复杂些的touch 事件的处理流程.说它复杂是因为key事件本身就key down,up,long pr ...

  9. android 数据存储Ⅱ

    本章继续讲解在Android开发中,数据的存储与管理.涉及知识点:SQLite,SwipeRefreshLayout控件刷新. 1.功能需求 练习使用SQLite 做一个登录界面,数据库字段包含用户名 ...

  10. 【转】Serverless架构

    这是来自martinfowler.com的Serverless架构一文的大意翻译. 什么是Serverless?    Serverless首先是用于描述我们的应用程序是明显或充分地依赖第三方应用或服 ...