pthread_cleanup_push()/pthread_cleanup_pop()

一、为什么会有pthread_cleanup_push与pthread_cleanup_pop:

一般来说,Posix的线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。

不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。

最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。

在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源 –从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用 pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。API定义如下:

void pthread_cleanup_push(void (*routine) (void  *),  void *arg)void pthread_cleanup_pop(int execute)

pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。


二、pthread_cleanup_push与pthread_cleanup_pop干什么用:

 

比如thread1:
执行
pthread_mutex_lock(&mutex);

//一些会阻塞程序运行的调用,比如套接字的accept,等待客户连接
sock = accept(……);            //这里是随便找的一个可以阻塞的接口

pthread_mutex_unlock(&mutex);
这个例子中,如果线程1执行accept时,线程会阻塞(也就是等在那里,有客户端连接的时候才返回,或则出现其他故障),线程等待中……

这时候线程2发现线程1等了很久,不赖烦了,他想关掉线程1,于是调用pthread_cancel()或者类似函数,请求线程1立即退出。

这时候线程1仍然在accept等待中,当它收到线程2的cancel信号后,就会从accept中退出,然后终止线程,注意这个时候线程1还没有执行:
pthread_mutex_unlock(&mutex);
也就是说锁资源没有释放,这回造成其他线程的死锁问题。

所以必须在线程接收到cancel后用一种方法来保证异常退出(也就是线程没达到终点)时可以做清理工作(主要是解锁方面),pthread_cleanup_push与pthread_cleanup_pop就是这样的。

pthread_cleanup_push(some_clean_func,…)
pthread_mutex_lock(&mutex);

//一些会阻塞程序运行的调用,比如套接字的accept,等待客户连接
sock = accept(……);            //这里是随便找的一个可以阻塞的接口

pthread_mutex_unlock(&mutex);
pthread_cleanup_pop(0);
return NULL;
上面的代码,如果accept被cancel后线程退出,会自动调用some_clean_func函数,在这个函数中你可以释放锁资源。如果accept没有被cancel,那么线程继续执行,当pthread_mutex_unlock(&mutex);表示线程自己正确的释放资源了,而执行pthread_cleanup_pop(0);也就是取消掉前面的some_clean_func函数。接着return线程就正确的结束了。

不晓得你明白没,通俗点就是:
pthread_cleanup_push注册一个回调函数,如果你的线程在对应的pthread_cleanup_pop之前异常退出(return是正常退出,其他是异常),那么系统就会执行这个回调函数(回调函数要做什么你自己决定)。但是如果在pthread_cleanup_pop之前没有异常退出,pthread_cleanup_pop就把对应的回调函数取消了,

关于取消点的解释:

比如你执行:
        printf(“thread sleep\n”);
        sleep(10);
        printf(“thread wake…\n”);
在sleep函数中,线程睡眠,结果收到cancel信号,这时候线程从sleep中醒来,但是线程不会立刻退出。这是应为pthread与C库方面的原因(具体是啥我也不清楚),pthread的建议是,如果一个函数是阻塞的,那么你必须在这个函数前后建立取消点,比如:
        printf(“thread sleep\n”);
        pthread_testcancel();
        sleep(10);
        pthread_testcancel();
        printf(“thread wake…\n”);

这样,就添加了两个取消掉。在执行到pthread_testcancel的位置时,线程才可能响应cancel退出进程。


三、需要注意:


原型很简单,功能跟atexit()差不多,只不过一个是线程一个是进程。用来设置在push/pop内线程退出时要做的事情。

pthread_cleanup_push与pthread_cleanup_pop是成对的宏实现的push与pop一定是成对出现的,其实push中包含“{“而pop中包含“}”,少一个不行。

“线程取消函数”即线程被取消或者下面描述的情况发生时自动调用的函数。它一般用于释放一些资源,比如释放锁,以免其它的线程永远 也不能获得锁,而造成死锁。
pthread_cleanup_push()函数执行压栈清理函数的操作,而pthread_cleanup_pop()函数执行从栈中删除清理函数的操作。

在下面三种情况下,pthread_cleanup_push()压栈的“清理函数”会被调用:


1, 线程调用pthread_exit()函数,而不是直接return.


2, 响应取消请求时,也就是有其它的线程对该线程调用pthread_cancel()函数。


3, 本线程调用pthread_cleanup_pop()函数,并且其参数非0


注意:
1.当pthread_cleanup_pop()函数的参数为0时,仅仅在线程调用pthread_exit函数或者其它线程对本线程调用 

     pthread_cancel函数时,才在弹出“清理函数”的同时执行该“清理函数”。


2.注意pthread_exit终止线程与线程直接return终止线程的区别,调用return函数是不会在弹出“清理函数”的同时执行该“清理函数的。


3 .pthread_cleanup_push()函数与pthread_cleanup_pop()函数必须成对的出现在同一个函数中。

4.在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault。


push进去的函数可能在以下三个时机执行:
1,显示的调用pthread_exit();

2,在cancel点线程被cancel。

3,pthread_cleanup_pop()的参数不为0时。

以上动作都限定在push/pop涵盖的代码内。

 

  1.  #include   
  2.  #include   
  3.  #include   
  4.  #include   
  5.  void * thr_fn(void *);  
  6.  void cleanup(void *);  
  7.  int main(int argc,char ** argv)  
  8.  {  
  9.         pthread_t tid;  
  10.         int err;  
  11.         void * tret;  
  12.   
  13.         err=pthread_create(&tid,NULL,thr_fn,NULL);  
  14.         if(err!=0)  
  15.               printf(”create thread error: %s\n”,strerror(err));  
  16.         err=pthread_join(tid,&tret);  
  17.         if(err!=0)  
  18.               printf(”thread join error: %s\n”,strerror(err));  
  19.         printf(”thread exit code %d\n”,(int)tret);  
  20.   
  21.         return 0;  
  22. }  
  23. void cleanup(void * arg)  
  24. {  
  25.         printf(”clean : %s\n”,(char *)arg);  
  26. }  
  27. void * thr_fn(void * arg)  
  28. {  
  29.         printf(”thread push start.\n”);  
  30.         pthread_cleanup_push(cleanup,”thread first handler”);  
  31.         pthread_cleanup_push(cleanup,”thread second handler”);  
  32.         printf(”thread push over.\n”);  
  33.   
  34.         pthread_exit((void *)1);  
  35.   
  36.         pthread_cleanup_pop(0);  
  37.         pthread_cleanup_pop(0);  
  38.         pthread_exit((void *)2);  
  39. }  
   #include 
   #include 
   #include 
   #include 
   void * thr_fn(void *);
   void cleanup(void *);
   int main(int argc,char ** argv)
   {
          pthread_t tid;
          int err;
          void * tret;

          err=pthread_create(&tid,NULL,thr_fn,NULL);
          if(err!=0)
                printf("create thread error: %s\n",strerror(err));
          err=pthread_join(tid,&tret);
          if(err!=0)
                printf("thread join error: %s\n",strerror(err));
          printf("thread exit code %d\n",(int)tret);

          return 0;
  }
  void cleanup(void * arg)
  {
          printf("clean : %s\n",(char *)arg);
  }
  void * thr_fn(void * arg)
  {
          printf("thread push start.\n");
          pthread_cleanup_push(cleanup,"thread first handler");
          pthread_cleanup_push(cleanup,"thread second handler");
          printf("thread push over.\n");

          pthread_exit((void *)1);

          pthread_cleanup_pop(0);
          pthread_cleanup_pop(0);
          pthread_exit((void *)2);
  }

额外的知识:
对于cancel信号,线程有两种方法: 忽略,和响应。默认是响应

接收到cancel信号,线程有两种处理类型: 立即响应 和 延迟响应(在最近的取消点响应),默认是延迟响应


参考:http://blog.csdn.NET/caianye/article/details/5912172

http://blog.chinaunix.Net/uid-20583479-id-1919902.html

http://blog.csdn.net/slj_win/article/details/7267483

  • 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、付费专栏及课程。

余额充值