zoukankan      html  css  js  c++  java
  • 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_pop()函数的参数为0时,仅仅在线程调用pthread_exit函数或者其它线程对本线程调用 pthread_cancel函数时,才在弹出“清理函数”的同时执行该“清理函数”)

    示例:

     1 /*两个线程都调用了,但是却只调用了第二个线程的清理处理程序,所以如果线程是通过从它的启动历程中返回而终止的话,那么它的清理处理程序就不会被调用,还要注意清理程序是按照与它们安装时相反的顺序被调用的。从代码输出也可以看到先执行的thread 2 second handler后执行的thread 2 first handler。
     2  */
     3 #include <stdio.h>
     4 #include <stdlib.h>
     5 #include <pthread.h>
     6 
     7 void cleanup(void *arg)
     8 {
     9     printf("cleanup:%s
    ",(char*)arg);
    10 }
    11 void *thr_fn1(void *arg)
    12 {
    13     printf("thread 1 start
    ");
    14     pthread_cleanup_push(cleanup,"thread 1 first handler");
    15     pthread_cleanup_push(cleanup,"thread 1 second handler");
    16     printf("thread 1 push complete
    ");
    17     if(arg)
    18         return ((void *)1);
    19     pthread_cleanup_pop(0);
    20     pthread_cleanup_pop(0);
    21     return ((void *)1);
    22 }
    23 void *thr_fn2(void *arg)
    24 {
    25     printf("thread 2 start
    ");
    26     pthread_cleanup_push(cleanup,"thread 2 first handler");
    27     pthread_cleanup_push(cleanup,"thread 2 second handler");
    28     printf("thread 2 push complete
    ");
    29     if(arg)
    30         pthread_exit((void *)2);
    31     pthread_cleanup_pop(0);
    32     pthread_cleanup_pop(0);
    33     pthread_exit((void *)2);
    34 }
    35 int main()
    36 {
    37     int err;
    38     pthread_t tid1,tid2;
    39     void *tret;
    40     err = pthread_create(&tid1,NULL,thr_fn1,(void *)1);
    41     if(err != 0)
    42     {
    43         fprintf(stderr,"thread create 1 is error
    ");
    44         return -1;
    45     }
    46     err = pthread_create(&tid2,NULL,thr_fn2,(void *)1);
    47     if(err != 0)
    48     {
    49         fprintf(stderr,"thread create 2 is error
    ");
    50         return -2;
    51     }
    52     err = pthread_join(tid1,&tret);
    53     if(err != 0)
    54     {
    55         fprintf(stderr,"can't join with thread 1
    ");
    56         return -2;
    57     }
    58 
    59     //pthread_cancel(tid1);
    60     printf("thread 1 exit code %d
    ",tret);
    61     err = pthread_join(tid2,&tret);
    62     if(err != 0)
    63     {
    64         fprintf(stderr,"can't join with thread 2
    ");
    65         return -2;
    66     }
    67     printf("thread 2 exit code %d
    ",tret);
    68     return 0;
    69 }
     1 #include<stdlib.h>
     2 #include<stdio.h>
     3 #include<unistd.h>
     4 #include<pthread.h>
     5 void clean_fun1(void * arg)
     6 {
     7     printf("this is clean fun1
    ");
     8 }
     9 void clean_fun2(void * arg)
    10 {
    11     printf("this is clean fun2
    ");
    12 }
    13 void * thread_fun(void * arg)
    14 {
    15     pthread_cleanup_push(clean_fun1,NULL);
    16     pthread_cleanup_push(clean_fun2,NULL);
    17     sleep(5);
    18     //这里要注意,如果将sleep(100);换成while(1);的话,程序会一直暂停.push和pop要成对出现.
    19     //因为while(1);运行的太快,线程不接受cancel信号
    20     //while(1);
    21     pthread_cleanup_pop(0);
    22     pthread_cleanup_pop(0);
    23     return NULL;
    24 }
    25 int main()
    26 {
    27     pthread_t tid1;
    28     int err;
    29     err=pthread_create(&tid1,NULL,thread_fun,NULL);
    30     if(err!=0)
    31     {
    32         perror("pthread_create");
    33         exit(0);
    34     }
    35     sleep(3);
    36     //printf("test
    ");
    37     err=pthread_cancel(tid1);
    38     if(err!=0)
    39     {
    40         perror("cancel error:");
    41         exit(0);
    42     }
    43     err=pthread_join(tid1,NULL);
    44     if(err!=0)
    45     {
    46         perror("pthread_join error:");
    47         exit(0);
    48     }
    49 
    50     return 0;
    51 }

    pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:

    #define pthread_cleanup_push(routine,arg)                                     
    { struct _pthread_cleanup_buffer _buffer;
    _pthread_cleanup_push (&_buffer, (routine), (arg));
    #define pthread_cleanup_pop(execute)
    _pthread_cleanup_pop (&_buffer, (execute)); }
    可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。
    work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。
    pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
    pthread_mutex_lock(&mut);
    /* do some work */
    pthread_mutex_unlock(&mut);
    pthread_cleanup_pop(0);
    必须要注意的是,如果线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在
    pthread_cleanup_push()和pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,从而导致清理函数unlock一个并没有加锁的
    mutex变量,造成错误。因此,在使用清理函数的时候,都应该暂时设置成PTHREAD_CANCEL_DEFERRED模式。为此,POSIX的
    Linux实现中还提供了一对不保证可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()扩展函数,功能与以下
    代码段相当:
    { int oldtype;
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
    pthread_cleanup_push(routine, arg);
    ...
    pthread_cleanup_pop(execute);
    pthread_setcanceltype(oldtype, NULL);
    }

    上面我用红色标记的部分是这两个函数的关键作用,我的理解就是:
    pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
    pthread_mutex_lock(&mut);
    /* do some work */
    pthread_mutex_unlock(&mut);
    pthread_cleanup_pop(0);
    本来do some work之后是有pthread_mutex_unlock(&mut);这句,也就是有解锁操作,但是在do some work时会出现非正常终止,那样的话,系统会根据pthread_cleanup_push中提供的函数,和参数进行解锁操作或者其他操作,以免造成死锁!

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

    线程清理函数

    “线程取消函数”即线程被取消或者下面描述的情况发生时自动调用的函数。它一般用于释放一些资源,比如释放锁,以免其它的线程永远 也不能获得锁,而造成死锁。
    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涵盖的代码内。

  • 相关阅读:
    共享纸巾更换主板代码分析 共享纸巾主板更换后的对接代码
    Python Django Ajax 传递列表数据
    Python Django migrate 报错解决办法
    Python 创建字典的多种方式
    Python 两个list合并成一个字典
    Python 正则 re.sub替换
    python Django Ajax基础
    Python Django 获取表单数据的三种方式
    python Django html 模板循环条件
    Python Django ORM 字段类型、参数、外键操作
  • 原文地址:https://www.cnblogs.com/guxuanqing/p/8385077.html
Copyright © 2011-2022 走看看