线程终止清理函数

线程终止清理函数

一个线程在执行的时候可以被其它线程cancel,也可能遇到程序崩溃等情况造成线程意外退出;假如线程此时申请了堆空间,或者加了锁之后,意外退出造成堆空间或者锁没有被清理,会出现比较严重的后果,尤其是锁!所以当正在运行的线程一旦出现意外退出后该怎么处理呢?-----使用线程终止清理函数!

函数原型:

//这是一对函数,需要成对的使用
void pthread_cleanup_push(void (*routine)(void *), void *arg);
//参数1:线程清理函数
//参数2:线程清理函数的参数

void pthread_cleanup_pop(int execute);
//参数:有两种值可以填,0或者非0值

这对函数对是采用栈结构来管理的清理函数的,调用pthread_cleanup_push()函数会将*routine指向的清理函数入栈,调用pthread_cleanup_pop()函数时,如果参数是非0值,就表示清理函数出栈,参数为0时,就表示不出栈!

代码示例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

void *threadFunc(void *p)
{
    char *p1 = (char *)malloc(20);
    strcpy(p1, "nihao");

    printf("p1 = %s\n", p1);

    free(p1);
    p1 = NULL;

    printf("p1 free success\n");

    pthread_exit(NULL);
}

int main(int argc, char **argv)
{
    pthread_t thid;
    pthread_create(&thid, NULL, threadFunc, NULL);

    //给子线程发送cancel信号
    pthread_cancel(thid);
    
    printf("main thread\n");

    pthread_join(thid, NULL);
    return 0;
}

//运行结果
main thread
p1 = nihao

显然上面的代码出现了内存泄露问题!下面使用线程终止清理函数来解决该问题!

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

void cleanFunc(void *p)
{
    printf("cleanfunc\n");
    free(p);
    p = NULL;
}

void *threadFunc(void *p)
{
    char *p1 = (char *)malloc(20);
    strcpy(p1, "nihao");
    //将清理函数cleanFunc入栈,并且将需要清理的资源传入进去
    pthread_cleanup_push(cleanFunc, p1);

    printf("p1 = %s\n", p1);

    free(p1);
    p1 = NULL;

    printf("p1 free success\n");

    //清理函数出栈
    pthread_cleanup_pop(1);
    pthread_exit(NULL);
}

int main(int argc, char **argv)
{
    pthread_t thid;
    pthread_create(&thid, NULL, threadFunc, NULL);

    //给子线程发送cancel信号
    pthread_cancel(thid);
    
    printf("main thread\n");

    pthread_join(thid, NULL);
    return 0;
}

//运行结果:
main thread
p1 = nihao
cleanfunc

此时看到这里,你脑海中应该有个疑问才对!为什么清理函数出栈的操作写在了29行,而线程是在21行被终止的,那么肯定运行不到29行,那么清理函数又是怎么得到执行的呢?

其实线程清理函数的到相应是有三个时机的!只要触发一个时机就会出栈,时机分别是:

  1. 线程收到cancel信号
  2. 线程主动调用pthread_cleanup_pop(非零值)
  3. 调用pthread_exit()函数

刚才的代码显然触发了时机1,收到cancel信号触发清理函数弹栈的!下面我将演示一些另外两个时机!

代码示例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

void cleanFunc(void *p)
{
    
    printf("cleanfunc\n");
    free(p);
    p = NULL;
}

void *threadFunc(void *p)
{
    char *p1 = (char *)malloc(20);
    strcpy(p1, "nihao");
    //将清理函数cleanFunc入栈,并且将需要清理的资源传入进去
    pthread_cleanup_push(cleanFunc, p1);

    printf("p1 = %s\n", p1);

    free(p1);
    p1 = NULL;

    printf("p1 free success\n");


    //清理函数出栈
    pthread_cleanup_pop(1);
    pthread_exit(NULL);
}

int main(int argc, char **argv)
{
    pthread_t thid;
    pthread_create(&thid, NULL, threadFunc, NULL);

    //给子线程发送cancel信号
    //注释掉该函数,防止触发线程清理函数出栈的第一个时机
    /* pthread_cancel(thid); */
    
    printf("main thread\n");

    pthread_join(thid, NULL);
    return 0;
}

//运行结果:
main thread
p1 = nihao
p1 free success
cleanfunc
free(): double free detected in tcache 2
已放弃 (核心已转储)

注意我上面代码注释掉了pthread_cancel(),上面代码显然是触发了线程清理函数的执行!下面演示最后一个触发时机!

代码示例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

void cleanFunc(void *p)
{
    
    printf("cleanfunc\n");
    free(p);
    p = NULL;
}

void *threadFunc(void *p)
{
    char *p1 = (char *)malloc(20);
    strcpy(p1, "nihao");
    //将清理函数cleanFunc入栈,并且将需要清理的资源传入进去
    pthread_cleanup_push(cleanFunc, p1);

    printf("p1 = %s\n", p1);

    free(p1);
    p1 = NULL;

    printf("p1 free success\n");

    pthread_exit(NULL);
    
    //将显示弹栈的操作放在了pthread_exit函数后面,为了清晰演示调用时机3,我将参数置为0.
    pthread_cleanup_pop(0);
}

int main(int argc, char **argv)
{
    pthread_t thid;
    pthread_create(&thid, NULL, threadFunc, NULL);

    //给子线程发送cancel信号
    //注释掉该函数,防止触发线程清理函数出栈的第一个时机
    /* pthread_cancel(thid); */
    
    printf("main thread\n");

    pthread_join(thid, NULL);
    return 0;
}

//运行结果
main thread
p1 = nihao
p1 free success
cleanfunc
free(): double free detected in tcache 2
已放弃 (核心已转储)

注意第31行代码我的修改,显然第三个清理时机也看到了!下面我将展示一个令你疑惑的代码!

代码示例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

void cleanFunc(void *p)
{
    
    printf("cleanfunc\n");
    free(p);
    p = NULL;
}

void *threadFunc(void *p)
{
    char *p1 = (char *)malloc(20);
    strcpy(p1, "nihao");
    //将清理函数cleanFunc入栈,并且将需要清理的资源传入进去
    pthread_cleanup_push(cleanFunc, p1);

    printf("p1 = %s\n", p1);

    free(p1);
    p1 = NULL;

    printf("p1 free success\n");

    //清理函数出栈
    pthread_cleanup_pop(0);
    pthread_exit(NULL);
}

int main(int argc, char **argv)
{
    pthread_t thid;
    pthread_create(&thid, NULL, threadFunc, NULL);

    //给子线程发送cancel信号
    //注释掉该函数,防止触发线程清理函数出栈的第一个时机
    /* pthread_cancel(thid); */
    
    printf("main thread\n");

    pthread_join(thid, NULL);
    return 0;
}

//运行结果
main thread
p1 = nihao
p1 free success

有童鞋蒙了,与前一个的代码长得一样啊,结果怎么不一样呢?靓仔,仔细看一下两个代码的pthread_cleanup_pop和pthread_exit函数的位置,明显的不一样么!那么为什么pop(0)写在了exit的前面就不会触发线程清理函数的执行呢?这就要讲一下原理了!

  1. 在调用push函数的时候,清理函数入栈,函数入栈的时候还会带一个标志位,这个标志位一开始位1,当调用pop函数出栈时,系统会检查pop的参数,如果参数为0时,就将函数的标志位置为0!否则不去改变标志位!
  2. 所以在最后的这个代码片段上,我先写的pop(0),所以提前将函数的标志位置为0,那么当调用exit函数的时候,发现标志位为0,那么就不会触发线程清理函数的出栈的!

清理函数对使用的注意事项:

  1. 清理函数对必须要成对的使用!
  2. 需要在同一层次的代码中使用清理函数对!

线程清理函数对的底层实现其实是宏!

define pthread_cleanup_push(routine, arg) \
do { \
	struct __pthread_cleanup_frame __clframe \
	__attribute__ ((__cleanup__ (__pthread_cleanup_routine))) \
	= { .__cancel_routine = (routine), .__cancel_arg = (arg), \
	.__do_it = 1 };
define pthread_cleanup_pop(execute) \
	__clframe.__do_it = (execute); \
} while (0)

可以清晰的看到pthread_cleanup_push宏实现中do后面只有一个**’{’,pthread_cleanup_pop宏实现中有一个’}’**后面有一个while(0);所以只有这两个宏成对出现的时候才能将{ }匹配成功!若了两个函数不在同一层代码中使用,也会导致大括号匹配出错的情况,造成代码逻辑错,如

//伪代码
void func()
{
	while(1){
		pthread_cleanup_push();
	}
	pthread_cleanup_pop();
}
//编译阶段会发生宏替换,想一下push的'{'是不是匹配到了while(1)的右边'}';而pop的'}'匹配到了while(1)的'{';导致代码逻辑错误!

根据经验来看,push函数尽量紧挨着需要清理的资源是比较稳妥的,防止资源与push函数之间发生线程意外退出,造成资源没有清理完成,pop函数一般尽量往后面放,但你可别放到pthread_exit()函数后面啊,放在后面都没有意义啊!

以上就是对线程终止清理函数的一些简单的使用总结,如有错误,望给位大佬不吝指正!如果你通过我的讲解有了收获记得三联哦!原创不易,转载请注明出处!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值