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

#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. windows根据端口号找进程

    d:\>netstat -ano | findstr "7777" TCP 127.0.0.1:7776 127.0.0.1:7777 ESTABLISHED 11764 T ...

  2. linux常用命令之文件系统

    df df - report file system disk space usage 查看文件系统的使用清空 用法 df [-hi] [path] 选项 -h human readable ,以人类 ...

  3. angular学习的一些小笔记(中)之双向数据绑定

    <!doctype html> <html ng-app=""> <head> <script src="https://aja ...

  4. 成为 Web 开发大师你必须知道的 7 件事情

    曾经是这样的,懂点编码,并可以偶尔耍点酷,那么你就会被认为是一个Web开发大师.但是现在,情况再也不是这样的了.Web开发已经朝着主流方向发展,开发人员数量显著增加.这意味着,如果你想成为这个领域的大 ...

  5. C++11新特性 lambda表达式

    C++11 添加了了一个名为lambda表达式的功能,可以用于添加匿名函数 语法: [capture_block](parameter) mutable exception_specification ...

  6. PyInstaller 安装方法 及简单的编译exe (python3)

    安装PyInstaller //地址 https://github.com/pyinstaller/pyinstaller/tree/python3 //上面的链接已经失效,新的(20160809更) ...

  7. arcmap Command

    The information in this document is useful if you are trying to programmatically find a built-in com ...

  8. Nmap源码分析(脚本引擎)

    Nmap提供了强大的脚本引擎(NSE),以支持通过Lua编程来扩展Nmap的功能.目前脚本库已经包含300多个常用的Lua脚本,辅助完成Nmap的主机发现.端口扫描.服务侦测.操作系统侦测四个基本功能 ...

  9. 检查sqlite数据库完整性

    最近遇到一个问题,用户数据丢失,拿到用户数据库文件以后,发现数据库损坏. database disk image is malformed 因此希望可以找到一种方法,可以检测出来数据库是否损坏,经过g ...

  10. 如何让光标处于EditText的末尾

    经测试发现,如果EditText预先有内容,光标自然会在文字的末尾 . 但是如果预先内容为空,然后设置好内容,这种情况下光标自然会在文字的开头,所以这种情况下可以这样做让光标位于末尾: editTex ...