• ZT pthread_cleanup_push()/pthread_cleanup_pop()的详解


    pthread_cleanup_push()/pthread_cleanup_pop()的详解

    分类: Linux 1271人阅读 评论(1) 收藏 举报

    刚练习线程的条件变量时碰到了这两个函数,关于这两个函数书上讲的比较模糊,所以在网上找到了一篇我感觉讲的很好的一篇文章,就是关于线程的终止,大概如下:



    一般来说,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()是以宏方式实现的,这是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。

  • 相关阅读:
    通过 SingleFlight 模式学习 Go 并发编程
    进程内优雅管理多个服务
    20192406梁健 202120222 《网络与系统攻防技术》实验四实验报告
    20192406梁健 202120222 《网络与系统攻防技术》实验五实验报告
    oracle存储过程迁移到PostgreSQL之问题总结
    frida 获取app里所有调用 java.lang.String的值
    firad 绕过ssl
    python frida 安装
    mitmproxy 夜神系统级证书
    MultiEntity AspectBased Sentiment Analysis with Context, Entity and Aspect Memory论文翻译
  • 原文地址:https://www.cnblogs.com/jeanschen/p/3622630.html
Copyright © 2020-2023  润新知