首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >线程清理(pthread_cleanup_push函数和pthread_cleanup_pop函数)

线程清理(pthread_cleanup_push函数和pthread_cleanup_pop函数)

作者头像
zy010101
发布2020-05-09 17:40:55
3.8K0
发布2020-05-09 17:40:55
举报
文章被收录于专栏:程序员程序员

看了一会儿,没看懂这个,绝了。书上写的颠三倒四。文档给每个函数两句话。也是没看懂啊!

先把代码试着跑一跑,调试一下,看看结果怎么样。

#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

void cleanup(void *arg)
{
    printf("clean...\n");
}

void *My_thread(void *arg)
{
    printf("My thread\n");
    pthread_cleanup_push(cleanup,"123");
    pthread_exit(NULL);        //虽然在这里,线程已经结束了,但是下面的pop函数还是得写上。
    pthread_cleanup_pop(0);
}

int main()
{
    pthread_t tid;
    int t = pthread_create(&tid,NULL,My_thread,NULL);
    pthread_join(tid,NULL);
    return 0;
}

运行结果如下所示:

解释一下,为什么pop函数必须得写上。这是因为它们可以被实现为宏。所以必须在与线程相同的作用域内以匹配的形式使用push函数和pop函数。pthread_cleanup_push的宏定义可以包含字符{,而pthread_cleanup_pop的宏定义必须有相对应的匹配字符}。

在Ubuntu16.04下,pthread_cleanup_push和pthread_cleanup_pop被实现为宏。当我们注释掉pop函数调用之后,再次编译,会发现报错如下:

当你发现这个错误,却无可奈何的时候,你甚至像我一样,检查了好几遍自己的代码。发现没有意料之外的结尾,所有的{}都是成对出现的。殊不知是pthread_cleanup_push和pthread_cleanup_pop被实现为宏。而且含有匹配的{}。

—————————既有趣,又很烦人的坑—————————

下面给出这两个宏的定义,就可以很直观的看到到底是怎么回事。

# define pthread_cleanup_push(routine, arg) \
  do {									      \
    __pthread_unwind_buf_t __cancel_buf;				      \
    void (*__cancel_routine) (void *) = (routine);			      \
    void *__cancel_arg = (arg);						      \
    int __not_first_call = __sigsetjmp ((struct __jmp_buf_tag *) (void *)     \
					__cancel_buf.__cancel_jmp_buf, 0);    \
    if (__glibc_unlikely (__not_first_call))				      \
      {									      \
	__cancel_routine (__cancel_arg);				      \
	__pthread_unwind_next (&__cancel_buf);				      \
	/* NOTREACHED */						      \
      }									      \
									      \
    __pthread_register_cancel (&__cancel_buf);				      \
    do {
extern void __pthread_register_cancel (__pthread_unwind_buf_t *__buf)
     __cleanup_fct_attribute;

//到这里,pthread_cleanup_push宏定义完了,而这个嵌套的do...while循环没完。
//它还有一半在pthread_cleanup_pop宏之中。

/* Remove a cleanup handler installed by the matching pthread_cleanup_push.
   If EXECUTE is non-zero, the handler function is called. */
# define pthread_cleanup_pop(execute) \
      do { } while (0);/* Empty to allow label before pthread_cleanup_pop.  */\
    } while (0);							      \
    __pthread_unregister_cancel (&__cancel_buf);			      \
    if (execute)							      \
      __cancel_routine (__cancel_arg);					      \
  } while (0)
extern void __pthread_unregister_cancel (__pthread_unwind_buf_t *__buf)
  __cleanup_fct_attribute;

在此之前,我还是个孩子,从来没有想过宏定义还能这样玩。程序界的前辈又给我上我一课。

好了,言归正传。我们接着看这两个宏到底怎么使用。把线程函数改为如下:

void *My_thread(void *arg)
{
    printf("My thread\n");
    pthread_cleanup_push(cleanup,"123");
    pthread_cleanup_pop(1);                   //非0参数
    pthread_exit(NULL);                       
}

运行结果如下:

然后继续更改线程函数如下:

void *My_thread(void *arg)
{
    printf("My thread\n");
    pthread_cleanup_push(cleanup,"123");

    pthread_cleanup_pop(0);                   //0参数
    pthread_exit(NULL);
}

运行结果如下:

下面取消线程函数,更改代码如下:

void *My_thread(void *arg)
{
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);     //设置取消线程立即生效
    printf("My thread\n");
    pthread_cleanup_push(cleanup,"123");
    pthread_cancel(pthread_self());         //取消该线程
 
    printf("线程执行不到这里\n");
   
    pthread_cleanup_pop(0);                 //0参数
}

运行结果如下:

没有打印“线程执行不到这里”这句话。

上述代码中得pthread_self()函数是用来获取正在调用它得线程的ID。而pthread_setcanceltype()函数是用来设置线程取消立即生效的,否则线程取消不是立即生效的。演示如下:

void *My_thread(void *arg)
{
    //pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL); 
    printf("My thread\n");
    pthread_cleanup_push(cleanup,"123");
    pthread_cancel(pthread_self());
    //sleep(1);                               //这个取消不是立即生效的,所以加上sleep(1)。也就是说,反正一切小心
    printf("线程执行不到这里\n");
    //pthread_exit(NULL);                     //虽然在这里,线程已经结束了,但是下面的pop函数还是得写上。
    pthread_cleanup_pop(0);                 //0参数
   // pthread_exit(NULL);
}

运行结果如下:

取消没有立即生效,仍旧打印了“线程执行不到这里”这句话。

既然说到这里了,这个pthread_cancle()函数真是绝了,和这个push,pop宏也差得不多。后面的文章再说这个cancle函数吧。

总结:清理函数是由push函数调度的。

  1. 调用pthread_exit()结束线程时;
  2. 响应取消线程请求时;
  3. 用非0的参数调用pthread_cleanup_pop()时。

当然了,无论什么情况,当pthread_cleanup_pop(0)被调用,那么清理函数将不会起作用。同时需要注意,一个线程可以有多个清理函数。清理程序记录在栈中。因此,一次pop只能取消最近一次的push。这也意味者它们的执行顺序和push注册的顺序是相反的。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-05-06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档