C语言 pthread_cleanup_push()和pthread_cleanup_pop()函数(用于临界资源程序段中发生终止动作后的资源清理任务,以免造成死锁,临界区资源一般上锁)

Posted Dontla

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言 pthread_cleanup_push()和pthread_cleanup_pop()函数(用于临界资源程序段中发生终止动作后的资源清理任务,以免造成死锁,临界区资源一般上锁)相关的知识,希望对你有一定的参考价值。

参考文章:pthread_cleanup_push()/pthread_cleanup_pop()的详解

brief:

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

关于上文作者的理解:

我的理解就是:

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。

但是我还是不太确定,什么情况必须要用清理函数来避免死锁,什么时候没必要用?。。。。

文章目录

man -f pthread_cleanup_push

PTHREAD_CLEANUP_PUSH(3)                                              Linux Programmer's Manual                                             PTHREAD_CLEANUP_PUSH(3)

NAME
       pthread_cleanup_push, pthread_cleanup_pop - push and pop thread cancellation clean-up handlers

SYNOPSIS
       #include <pthread.h>

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

       Compile and link with -pthread.

DESCRIPTION
       These functions manipulate the calling thread's stack of thread-cancellation clean-up handlers.  A clean-up handler is a function that is automatically executed when a thread is canceled (or in various other circumstances described below); it might, for example, unlock a mutex so that it becomes available to other threads in the process.
       //这些函数操纵调用线程的线程取消清理处理程序堆栈。 
       //清理处理程序是一个在线程被取消时自动执行的函数(或在下面描述的各种其他情况下); 
       //例如,它可能会解锁一个互斥体,以便进程中的其他线程可以使用它。

       The pthread_cleanup_push() function pushes routine onto the top of the stack of clean-up handlers.  When routine is later invoked, it will be given arg as its argument.
       //pthread_cleanup_push() 函数将例程推送到清理处理程序堆栈的顶部。 
       //稍后调用例程时,将给定 arg 作为其参数。
       //(例程就是cleanup_handler)

       The pthread_cleanup_pop() function removes the routine at the top of the stack of clean-up handlers, and optionally executes it if execute is nonzero.
       //pthread_cleanup_pop() 函数删除清理处理程序堆栈顶部的例程,
       //如果 execute 不为零,则可选择执行它。

       A cancellation clean-up handler is popped from the stack and executed in the following circumstances:
       //cancellation 清理处理程序从堆栈中弹出并在以下情况下执行:

       1. When a thread is canceled, all of the stacked clean-up handlers are popped and executed in the reverse of the order in which they were pushed onto the stack.
		//当一个线程被取消时,所有堆叠的清理处理程序都被弹出并按照它们被压入堆栈的相反顺序执行。

       2. When a thread terminates by calling pthread_exit(3), all clean-up handlers are executed as described in the preceding point.  (Clean-up handlers are not called if the thread terminates by performing a return from the thread start function.)
		//当一个线程通过调用 pthread_exit(3) 终止时,所有的清理处理程序都会按照前一点的描述执行。 
		//(如果线程通过从线程启动函数执行返回而终止,则不会调用清理处理程序。)
		
       3. When a thread calls pthread_cleanup_pop() with a nonzero execute argument, the top-most clean-up handler is popped and executed.
       //当线程使用非零执行参数调用 pthread_cleanup_pop() 时,会弹出并执行最顶层的清理处理程序。

       POSIX.1 permits pthread_cleanup_push() and pthread_cleanup_pop() to be implemented as macros that expand to text containing '' and '', respectively.  For this reason, the caller must ensure that calls to these functions are paired within the  same
       function, and at the same lexical nesting level.  (In other words, a clean-up handler is established only during the execution of a specified section of code.)
       //POSIX.1 允许将 pthread_cleanup_push() 和 pthread_cleanup_pop() 实现为分别扩展为包含“”和“”的文本的宏。 
       //出于这个原因,调用者必须确保对这些函数的调用在同一个函数中成对出现,并且处于相同的词法嵌套级别。 (换句话说,清理处理程序仅在执行指定的代码段期间建立。)

       Calling longjmp(3) (siglongjmp(3)) produces undefined results if any call has been made to pthread_cleanup_push() or pthread_cleanup_pop() without the matching call of the pair since the jump buffer was filled by setjmp(3) (sigsetjmp(3)).  Likewise,
       calling longjmp(3) (siglongjmp(3)) from inside a clean-up handler produces undefined results unless the jump buffer was also filled by setjmp(3) (sigsetjmp(3)) inside the handler.
       //如果由于 setjmp(3) 填充了跳转缓冲区 (sigsetjmp(3 ))。 
       //同样,从清理处理程序内部调用 longjmp(3) (siglongjmp(3)) 会产生未定义的结果,除非跳转缓冲区也由处理程序内部的 setjmp(3) (sigsetjmp(3)) 填充。

RETURN VALUE
       These functions do not return a value.
       //这些函数不返回值。

ERRORS
       There are no errors.

ATTRIBUTES
       For an explanation of the terms used in this section, see attributes(7).

       ┌────────────────────────┬───────────────┬─────────┐
       │Interface               │ Attribute     │ Value   │
       ├────────────────────────┼───────────────┼─────────┤
       │pthread_cleanup_push(), │ Thread safety │ MT-Safe │
       │pthread_cleanup_pop()   │               │         │
       └────────────────────────┴───────────────┴─────────┘

CONFORMING TO
       POSIX.1-2001, POSIX.1-2008.

NOTES
       On Linux, the pthread_cleanup_push() and pthread_cleanup_pop() functions are implemented as macros that expand to text containing '' and '', respectively.  This means that variables declared within the scope of paired calls to these functions will
       be visible within only that scope.
       //在 Linux 上,pthread_cleanup_push() 和 pthread_cleanup_pop() 函数实现为宏,分别扩展为包含“”和“”的文本。 
       //这意味着在对这些函数的成对调用范围内声明的变量将仅在该范围内可见。

       POSIX.1 says that the effect of using return, break, continue, or goto to prematurely leave a block bracketed pthread_cleanup_push() and pthread_cleanup_pop() is undefined.  Portable applications should avoid doing this.
       //POSIX.1 表示使用 return、break、continue 或 goto 过早地离开带有 pthread_cleanup_push() 和 pthread_cleanup_pop() 的块的效果是未定义的。 
       //可移植应用程序应避免这样做。
       
EXAMPLE
       The program below provides a simple example of the use of the functions described in this page.  The program creates a thread that executes a loop bracketed by pthread_cleanup_push() and pthread_cleanup_pop().  This loop increments  a  global  vari‐
       able,  cnt,  once  each second.  Depending on what command-line arguments are supplied, the main thread sends the other thread a cancellation request, or sets a global variable that causes the other thread to exit its loop and terminate normally (by
       doing a return).
       //下面的程序提供了一个使用本页所述功能的简单示例。 
       //该程序创建一个执行由 pthread_cleanup_push() 和 pthread_cleanup_pop() 括起来的循环的线程。
       // 这个循环每秒递增一次全局变量 cnt。 
       //根据提供的命令行参数,主线程向另一个线程发送取消请求,或设置一个全局变量,使另一个线程退出其循环并正常终止(通过返回)。

       In the following shell session, the main thread sends a cancellation request to the other thread:
       //在以下 shell 会话中,主线程向另一个线程发送取消请求:

           $ ./a.out
           New thread started
           cnt = 0
           cnt = 1
           Canceling thread
           Called clean-up handler
           Thread was canceled; cnt = 0

       From the above, we see that the thread was canceled, and that the cancellation clean-up handler was called and it reset the value of the global variable cnt to 0.
       //从上面,我们看到线程被取消了,取消清理处理程序被调用,并将全局变量 cnt 的值重置为 0。

       In the next run, the main program sets a global variable that causes other thread to terminate normally:
       //在下一次运行中,主程序设置了一个全局变量,导致其他线程正常终止:

           $ ./a.out x
           New thread started
           cnt = 0
           cnt = 1
           Thread terminated normally; cnt = 2

       From the above, we see that the clean-up handler was not executed (because cleanup_pop_arg was 0), and therefore the value of cnt was not reset.
       //从上面我们可以看出,清理处理程序没有被执行(因为 cleanup_pop_arg 为 0),因此 cnt 的值没有被重置。

       In the next run, the main program sets a global variable that causes the other thread to terminate normally, and supplies a nonzero value for cleanup_pop_arg:
       //在下一次运行中,主程序设置一个全局变量,使其他线程正常终止,并为 cleanup_pop_arg 提供一个非零值:

           $ ./a.out x 1
           New thread started
           cnt = 0
           cnt = 1
           Called clean-up handler
           Thread terminated normally; cnt = 0

       In the above, we see that although the thread was not canceled, the clean-up handler was executed, because the argument given to pthread_cleanup_pop() was nonzero.
       //在上面,我们看到虽然线程没有被取消,但是清理处理程序被执行了,因为给 pthread_cleanup_pop() 的参数是非零的。

   Program source

       #include <pthread.h>
       #include <sys/types.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <unistd.h>
       #include <errno.h>

       #define handle_error_en(en, msg) \\
               do  errno = en; perror(msg); exit(EXIT_FAILURE);  while (0)

       static int done = 0;
       static int cleanup_pop_arg = 0;
       static int cnt = 0;

       static void
       cleanup_handler(void *arg)
       
           printf("Called clean-up handler\\n");
           cnt = 0;
       

       static void *
       thread_start(void *arg)
       
           time_t start, curr;

           printf("New thread started\\n");

           pthread_cleanup_push(cleanup_handler, NULL);

           curr = start = time(NULL);

           while (!done) 
               pthread_testcancel();           /* A cancellation point */
               if (curr < time(NULL)) 
                   curr = time(NULL);
                   printf("cnt = %d\\n", cnt);  /* A cancellation point */
                   cnt++;
               
           

           pthread_cleanup_pop(cleanup_pop_arg);
           return NULL;
       

       int
       main(int argc, char *argv[])
       
           pthread_t thr;
           int s;
           void *res;

           s = pthread_create(&thr, NULL, thread_start, NULL);
           if (s != 0)
               handle_error_en(s, "pthread_create");

           sleep(2);           /* Allow new thread to run a while */

			

           if (argc > 1) 
               if (argc > 2)
                   cleanup_pop_arg = atoi(argv[2]);
               done = 1;

            else 
               printf("Canceling thread\\n");
               s = pthread_cancel(thr);
               if (s != 0)
                   handle_error_en(s, "pthread_cancel");
           

           s = pthread_join(thr, &res);
           if (s != 0)
               handle_error_en(s, "pthread_join");

           if (res == PTHREAD_CANCELED)
               printf("Thread was canceled; cnt = %d\\n", cnt);
           else
               printf("Thread terminated normally; cnt = %d\\n", cnt);
           exit(EXIT_SUCCESS);
       

SEE ALSO
       pthread_cancel(3), pthread_cleanup_push_defer_np(3), pthread_setcancelstate(3), pthread_testcancel(3), pthreads(7)

COLOPHON
       This page is part of release 4.04 of the Linux man-pages project.  A description of the project, information about reporting bugs, and the latest version of this page, can be found at http://www.kernel.org/doc/man-pages/.

Linux                                                                                                                      2015-07-23                                                                                                    PTHREAD_CLEANUP_PUSH(3)
 Manual page pthread_cleanup_push(3) line 121/189 (END) (press h for help or q to quit)

上面的示例代码

pthread_cleanup_example.c

#include <pthread.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#define handle_error_en(en, msg) \\
        do  errno = en; perror(msg); exit(EXIT_FAILURE);  while (0)

static int done = 0;
static int cleanup_pop_arg = 0;
static int cnt = 0;

static void
cleanup_handler(void *arg)

    printf("Called clean-up handler\\n");
    cnt = 0;


static void *
thread_start(void *arg)

    time_t start, curr;

    printf("New thread started\\n");

    pthread_cleanup_push(cleanup_handler, NULL);

    curr = start = time(NULL);

    while (!done) 
        pthread_testcancel();           /* A cancellation point */
        if (curr < time(NULL)) 
            curr = time(NULL);
            printf("cnt = %d\\n", cnt);  /* A cancellation point */
            cnt++;
        
    

    pthread_cleanup_pop(cleanup_pop_arg);
    return NULL;


int main(int argc, char *argv[])

    pthread_t thr;
    int s;
    void *res;

    s = pthread_create(&thr, NULL, thread_start, NULL);
    if (s != 0)
        handle_error_en(s, "pthread_create");

    sleep(2);           /* Allow new thread to run a while 允许新线程运行一段时间 */

    if (argc > 1) 
        if (argc > 2)
            cleanup_pop_arg = atoi(argv[2]);
        done = 1;

     else 
        printf("Canceling thread\\n");
        s = pthread_cancel(thr);
        if (s != 0)
            handle_error_en(s, "pthread_cancel");
    

    s = pthread_join(thr, &res);
    if (s != 0)
        handle_error_en(s, "pthread_join");

    if (res == PTHREAD_CANCELED)
        printf("Thread was canceled; cnt = %d\\n", cnt);
    else
        printf("Thread terminated normally; cnt = %d\\n", cnt);
    exit(EXIT_SUCCESS);

以上是关于C语言 pthread_cleanup_push()和pthread_cleanup_pop()函数(用于临界资源程序段中发生终止动作后的资源清理任务,以免造成死锁,临界区资源一般上锁)的主要内容,如果未能解决你的问题,请参考以下文章

pthread_cleanup_push vs Autorelease VS 异常处理

pthread中将处理程序送到堆栈上

[pthread]Linux C 多线程简单示例

POSIX 线程清理函数

C语言和ARDUINO语言一样吗

“C语言是中间语言”,中间语言是啥意思?