linux pthread_cleanup_push 线程实现,线程清理(pthread_cleanup_push函数和pthread_cleanup_pop函数)...

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

2f55b24f8b8ea342e916eff971dcd4af.png

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

#include

#include

#include

#include

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;

}

运行结果如下所示:

ea89ed07709f3604050d01330643fd2e.png

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

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

01daeabcfc8e4b32af59aa761c97081e.png

当你发现这个错误,却无可奈何的时候,你甚至像我一样,检查了好几遍自己的代码。发现没有意料之外的结尾,所有的{}都是成对出现的。殊不知是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);

}

运行结果如下:

192f88e092c817ffca396efbfc7561fb.png

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

void *My_thread(void *arg)

{

printf("My thread\n");

pthread_cleanup_push(cleanup,"123");

pthread_cleanup_pop(0); //0参数

pthread_exit(NULL);

}

运行结果如下:

b09965791d43722c318341ef4d560c11.png

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

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参数

}

运行结果如下:

cc80218d44f3412ca806fcf1880cd7dd.png

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

上述代码中得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);

}

运行结果如下:

709c71d14d7a8ce5ddc4df6b1e8fd59d.png

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

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

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

调用pthread_exit()结束线程时;

响应取消线程请求时;

用非0的参数调用pthread_cleanup_pop()时。

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

本文同步分享在 博客“zy010101”(CSDN)。

如有侵权,请联系 support@oschina.cn 删除。

本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`pthread_cleanup_push` 和 `pthread_cleanup_pop` 是 POSIX 线程库提供的两个函数,用于在线程退出时清理资源。 具体来说,当线程执行到 `pthread_cleanup_push` 函数时,它会将一个清理函数和一个参数压入线程清理栈中。当线程退出时,无论是通过线程函数的 return 语句、pthread_exit 函数还是被取消,都会自动调用清理栈中的每个清理函数,并按照压入栈的顺序依次执行。 在多线程编程中,线程可能会因为各种原因(如出现异常)而异常终止,导致没有机会清理资源,从而造成资源泄漏或者其他问题。使用 `pthread_cleanup_push` 和 `pthread_cleanup_pop` 可以保证线程退出时一定会执行清理函数,避免这些问题。 下面是一个示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> void cleanup_handler(void *arg) { printf("cleanup: %s\n", (char *) arg); } void *thread_func(void *arg) { char *msg = (char *) arg; printf("thread: %s\n", msg); // 压入清理函数 pthread_cleanup_push(cleanup_handler, "thread is finished"); // 执行任务 sleep(5); // 弹出清理函数 pthread_cleanup_pop(1); pthread_exit(NULL); } int main() { pthread_t tid; int ret; ret = pthread_create(&tid, NULL, thread_func, "hello world"); if (ret != 0) { fprintf(stderr, "pthread_create error\n"); exit(EXIT_FAILURE); } // 等待线程退出 ret = pthread_join(tid, NULL); if (ret != 0) { fprintf(stderr, "pthread_join error\n"); exit(EXIT_FAILURE); } printf("main: thread is finished\n"); return 0; } ``` 在这个示例中,线程执行到 `pthread_cleanup_push` 函数时,它会将 `cleanup_handler` 函数和字符串 `"thread is finished"` 压入清理栈中。当线程执行完任务后,无论是正常退出还是被取消,都会自动调用 `cleanup_handler` 函数,并打印出 `"cleanup: thread is finished"` 的消息。 需要注意的是,如果在 `pthread_cleanup_push` 和 `pthread_cleanup_pop` 之间调用了 `pthread_exit`,那么清理函数也会被执行。但是如果在 `pthread_cleanup_push` 和 `pthread_cleanup_pop` 之间调用了 `longjmp`,那么清理函数就不会被执行。因此,不要在使用 `setjmp` 和 `longjmp` 的代码中使用 `pthread_cleanup_push` 和 `pthread_cleanup_pop`。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值