線程清理(pthread_cleanup_push函數和pthread_cleanup_pop函數)

看了一會兒,沒看懂這個,絕了。書上寫的顛三倒四。文檔給每個函數兩句話。也是沒看懂啊!

先把代碼試着跑一跑,調試一下,看看結果怎麼樣。

#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

void cleanup(void *arg)
{
    printf("clean...\n");
}

void *My_thread(void *arg)
{
    printf("My thread\n");
    pthread_cleanup_push(cleanup,"123");
    pthread_exit(NULL);        //雖然在這裏,線程已經結束了,但是下面的pop函數還是得寫上。
    pthread_cleanup_pop(0);
}

int main()
{
    pthread_t tid;
    int t = pthread_create(&tid,NULL,My_thread,NULL);
    pthread_join(tid,NULL);
    return 0;
}

運行結果如下所示:

解釋一下,爲什麼pop函數必須得寫上。這是因爲它們可以被實現爲宏。所以必須在與線程相同的作用域內以匹配的形式使用push函數和pop函數。pthread_cleanup_push的宏定義可以包含字符{,而pthread_cleanup_pop的宏定義必須有相對應的匹配字符}。

在Ubuntu16.04下,pthread_cleanup_push和pthread_cleanup_pop被實現爲宏。當我們註釋掉pop函數調用之後,再次編譯,會發現報錯如下:

當你發現這個錯誤,卻無可奈何的時候,你甚至像我一樣,檢查了好幾遍自己的代碼。發現沒有意料之外的結尾,所有的{}都是成對出現的。殊不知是pthread_cleanup_push和pthread_cleanup_pop被實現爲宏。而且含有匹配的{}。

                                         —————————既有趣,又很煩人的坑—————————

下面給出這兩個宏的定義,就可以很直觀的看到到底是怎麼回事。

# define pthread_cleanup_push(routine, arg) \
  do {									      \
    __pthread_unwind_buf_t __cancel_buf;				      \
    void (*__cancel_routine) (void *) = (routine);			      \
    void *__cancel_arg = (arg);						      \
    int __not_first_call = __sigsetjmp ((struct __jmp_buf_tag *) (void *)     \
					__cancel_buf.__cancel_jmp_buf, 0);    \
    if (__glibc_unlikely (__not_first_call))				      \
      {									      \
	__cancel_routine (__cancel_arg);				      \
	__pthread_unwind_next (&__cancel_buf);				      \
	/* NOTREACHED */						      \
      }									      \
									      \
    __pthread_register_cancel (&__cancel_buf);				      \
    do {
extern void __pthread_register_cancel (__pthread_unwind_buf_t *__buf)
     __cleanup_fct_attribute;

//到這裏,pthread_cleanup_push宏定義完了,而這個嵌套的do...while循環沒完。
//它還有一半在pthread_cleanup_pop宏之中。

/* Remove a cleanup handler installed by the matching pthread_cleanup_push.
   If EXECUTE is non-zero, the handler function is called. */
# define pthread_cleanup_pop(execute) \
      do { } while (0);/* Empty to allow label before pthread_cleanup_pop.  */\
    } while (0);							      \
    __pthread_unregister_cancel (&__cancel_buf);			      \
    if (execute)							      \
      __cancel_routine (__cancel_arg);					      \
  } while (0)
extern void __pthread_unregister_cancel (__pthread_unwind_buf_t *__buf)
  __cleanup_fct_attribute;

在此之前,我還是個孩子,從來沒有想過宏定義還能這樣玩。程序界的前輩又給我上我一課。

好了,言歸正傳。我們接着看這兩個宏到底怎麼使用。把線程函數改爲如下:

void *My_thread(void *arg)
{
    printf("My thread\n");
    pthread_cleanup_push(cleanup,"123");
    pthread_cleanup_pop(1);                   //非0參數
    pthread_exit(NULL);                       
}

運行結果如下:

 然後繼續更改線程函數如下:

void *My_thread(void *arg)
{
    printf("My thread\n");
    pthread_cleanup_push(cleanup,"123");

    pthread_cleanup_pop(0);                   //0參數
    pthread_exit(NULL);
}

運行結果如下:

下面取消線程函數,更改代碼如下:

void *My_thread(void *arg)
{
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);     //設置取消線程立即生效
    printf("My thread\n");
    pthread_cleanup_push(cleanup,"123");
    pthread_cancel(pthread_self());         //取消該線程
 
    printf("線程執行不到這裏\n");
   
    pthread_cleanup_pop(0);                 //0參數
}

運行結果如下:

沒有打印“線程執行不到這裏”這句話。

上述代碼中得pthread_self()函數是用來獲取正在調用它得線程的ID。而pthread_setcanceltype()函數是用來設置線程取消立即生效的,否則線程取消不是立即生效的。演示如下:

void *My_thread(void *arg)
{
    //pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL); 
    printf("My thread\n");
    pthread_cleanup_push(cleanup,"123");
    pthread_cancel(pthread_self());
    //sleep(1);                               //這個取消不是立即生效的,所以加上sleep(1)。也就是說,反正一切小心
    printf("線程執行不到這裏\n");
    //pthread_exit(NULL);                     //雖然在這裏,線程已經結束了,但是下面的pop函數還是得寫上。
    pthread_cleanup_pop(0);                 //0參數
   // pthread_exit(NULL);
}

運行結果如下:

取消沒有立即生效,仍舊打印了“線程執行不到這裏”這句話。

既然說到這裏了,這個pthread_cancle()函數真是絕了,和這個push,pop宏也差得不多。後面的文章再說這個cancle函數吧。

總結:清理函數是由push函數調度的。

  1. 調用pthread_exit()結束線程時;
  2. 響應取消線程請求時;
  3. 用非0的參數調用pthread_cleanup_pop()時。

當然了,無論什麼情況,當pthread_cleanup_pop(0)被調用,那麼清理函數將不會起作用。同時需要注意,一個線程可以有多個清理函數。清理程序記錄在棧中。因此,一次pop只能取消最近一次的push。這也意味者它們的執行順序和push註冊的順序是相反的。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章