• pthread_cleanup_push与pthread_cleanup_pop的理解


    一、为什么会有pthread_cleanup_pushpthread_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_pushpthread_cleanup_pop干什么用:

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

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

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

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

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

    所以必须在线程接收到cancel后用一种方法来保证异常退出(也就是线程没达到终点)时可以做清理工作(主要是解锁方面)pthread_cleanup_pushpthread_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;
    上面的代码,如果acceptcancel后线程退出,会自动调用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 ");
            sleep(10);
            printf("thread wake... ");
    sleep函数中,线程睡眠,结果收到cancel信号,这时候线程从sleep中醒来,但是线程不会立刻退出。这是应为pthreadC库方面的原因(具体是啥我也不清楚)pthread建议是,如果一个函数是阻塞的,那么你必须在这个函数前后建立取消点,比如:
            printf("thread sleep ");
            pthread_testcancel()
            sleep(10);
            pthread_testcancel()
            printf("thread wake... ");

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


    三、需要注意:


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

    pthread_cleanup_pushpthread_cleanup_pop是成对的宏实现的pushpop一定是成对出现的,其实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

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

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

       #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
    ",strerror(err));
              err=pthread_join(tid,&tret);
              if(err!=0)
                    printf("thread join error: %s
    ",strerror(err));
              printf("thread exit code %d
    ",(int)tret);
      
              return 0;
      }
      void cleanup(void * arg)
      {
              printf("clean : %s
    ",(char *)arg);
      }
      void * thr_fn(void * arg)
      {
              printf("thread push start.
    ");
              pthread_cleanup_push(cleanup,"thread first handler");
              pthread_cleanup_push(cleanup,"thread second handler");
              printf("thread push over.
    ");
     
              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

  • 相关阅读:
    改造MFC程序,使原来不支持winsocket的工程支持winsocket
    算术移位和逻辑移位实现分析
    MFC 编辑框中字体大小改变,行高不能改变,只能显示一半的问题,已解决。
    在MFC中,使用控制台Console输出调试信息
    在MFC中使用GDI+的一般方法,以VC6.0编译器为例
    WinForm 实现主程序(exe)与其关联类库(*.dll)分开存放
    Deserializing/Serializing SOAP Messages in C#
    List分页
    ConvertJavaMiliSecondToDateTime
    中文数字大小写转阿拉伯数字
  • 原文地址:https://www.cnblogs.com/Zoran-/p/5819282.html
Copyright © 2020-2023  润新知