線程等待退出、退出清理函數


1.等待線程退出:

          線程從入口點函數自然返回,或者主動調用pthread_exit()函數,都可以讓線程正常終止。

          線程從入口點函數自然返回時,函數返回值可以被其它線程用pthread_join函數獲取。

pthread_join原型為:

      #include <pthread.h>

   int pthread_join(pthread_t th, void **thread_return);


1.該函數是一個阻塞函數一直等到參數 th 指定的線程返回與多進程中的waitwaitpid類似。

thread_return是一個傳出參數,接收線程函數的返回值。如果線程通過調用pthread_exit()終止,則 pthread_exit() 中的參數相當於自然返回值,照樣可以被其它線程用pthread_join()獲取到


2.thid傳遞0值時,join返回ESRCH錯誤。

3.該函數還有一個非常重要的作用,由於一個進程中的多個線程共享數據段,因此通常在一個線程退出后,退出線程所占用的資源並不會隨線程結束而釋放。如果th線程類型並不是自動清理資源類型的,則th線程退出后,線程本身的資源必須通過其它線程調用pthread_join來清除,這相當於多進程程序中的waitpid
pthread_join.c
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
void* threadfunc(void*p)
{
        strcpy((char*)p,"hello");
        printf("I am a child thread %s\n",(char*)p);
        printf("child p is %p\n",p);
        pthread_exit(p);
}
int main()
{
        pthread_t thdid;
        void* p=malloc(20);
        printf("father p is %p\n",p);
        int ret = pthread_create(&thdid,NULL,threadfunc,p);
        if(ret!=0)
        {
                printf("error pthread_create\n");
                return -1;
        }
        printf("I am a father thread %s\n",(char*)p);
        void *p1;
        printf("return thread  p1 is %p\n",p1);
        ret=pthread_join(thdid,&p1);
        if(ret!=0)
        {
                printf("error pthread_join\n");
                return -1;
        } 
        printf("father thread %s\n",(char*)p);
        printf("return thread  p1 is %p\n",p1);
        printf("retturn thread p1=%s\n",(char*)p1);
        return 0;
}




線程也可以被其它線程殺掉,在Linux中的說法是一個線程被另一個線程取消(cancel)

       線程取消的方法是一個線程向目標線程發cancel信號,但是如何處理cancel信號則由目標線程自己決定,目標線程或者忽略、或者立即終止、或者繼續運行cancelation-point(取消點)后終止


取消點:

       根據POSIX標准,pthread_join()pthread_testcancel()pthread_cond_wait()pthread_cond_timedwait()sem_wait()sigwait()等函數以及read()write()等會引起阻塞的系統調用都是Cancelation-point而其他pthread函數都不會引起Cancelation動作。但是pthread_cancel的手冊頁聲稱,由於Linux線程庫與C庫結合得不好,因而目前C庫函數都不是Cancelation-point;但CANCEL信號會使線程從阻塞的系統調用中退出,並置EINTR錯誤碼,因此可以在需要作為Cancelation-point的系統調用前后調用pthread_testcancel(),從而達到POSIX標准所要求的目標,即如下代碼段:

    

pthread_testcancel();
r etcode = read(fd, buffer, length);
pthread_testcancel();

     

      但是從RedHat9.0的實際測試來看,至少有些C庫函數的阻塞函數是取消點,如read(),getchar()等,而sleep()函數不管線程是否設置了pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL),都起到取消點作用。總之,線程的取消一方面是一個線程強行殺另外一個線程,從程序設計角度看並不是一種好的風格,另一方面目前Linux本身對這方面的支持並不完善,所以在實際應用中應該謹慎使用!!


       int pthread_cancel(pthread_t thread);         //盡量不要用,linux支持並不完善


 

man函數不能查看函數幫助 增加 man 信息 sudo apt-get install manpages-posix-dev
pthread_cancel.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
//驗證主線程cancel子線程場景
void* thread(void* p)
{
        printf("I am child\n");
        char buf[128]={0};
        read(0,buf,sizeof(buf));
        printf("after read\n");
        pthread_exit((void*)5);
}

int main()
{
        pthread_t pth_id;
        pthread_create(&pth_id,NULL,thread,NULL);
        int ret;
        //sleep(2);    //讓線程正常執行完退出
        ret=pthread_cancel(pth_id);  //不用等待讓線程創建好,傳入pth_id可以直接取消后續操作
        if(ret!=0)
        {
                printf("pthread_cancel failed ret=%d\n",ret);
                return -1;
        }
        int i;
        ret=pthread_join(pth_id,(void**)&i);    //不獲取子線程的返回值
        if(ret!=0)
        {
                printf("pthread_join failed ret=%d\n",ret);
                return -1;
        }
        printf("main thread i = %d\n",(int)i);
        return 0;
}


#define        ESRCH                 3        /* No such process */
線程被取消,退出值就是-1,這個值還是能被pthread_join函數捕捉到


所有的內核線程運行在同一個內核進程地址空間,這個和用戶級線程不一樣



3. 線程終止清理函數

     不論是可預見的線程終止還是異常終止,都會存在資源釋放的問題,在不考慮因運行出錯而退出的前提下,如何保證線程終止時能順利的釋放掉自己所占用的資源,特別是鎖資源,就是一個必須考慮解決的問題。

       最經常出現的情形是資源獨占鎖的使用:線程為了訪問臨界共享資源而為其加上鎖,但在訪問過程中該線程被外界取消,或者發生了中斷,則該臨界資源將永遠處於鎖定狀態得不到釋放。外界取消操作是不可預見的,因此的確需要一個機制來簡化用於資源釋放的編程。(比如子線程malloc的空間,等到子線程退出,這段空間並沒有釋放)

       POSIX線程API中提供了一個pthread_cleanup_push()/pthread_cleanup_pop()函數對用於自動釋放資源--pthread_cleanup_push()的調用點到pthread_cleanup_pop()之間的程序段中的終止動作都將執行pthread_cleanup_push()所指定的清理函數API定義如下:


   void   pthread_cleanup_push (void (*routine) (void *),   void *arg)

   void   pthread_cleanup_pop (int execute)                //(默認都寫0)


       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()帶有一個"}",因此這兩個函數必須成對出現,且必須位於程序的同一級別的代碼段中才能通過編譯。;宏定義中'\'表示連接到下一行;

pthread_cleanup_pop 的參數 execute 如果為非0,則按棧的順序注銷掉一個原來注冊的清理函數,並執行該函數;當 pthread_cleanup_pop() 函數的參數為0時,僅僅在線程調用pthread_exit函數或者其它線程對本線程調用pthread_cancel函數時,才在彈出清理函數的同時執行該清理函數

pthread_cleanup.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

//驗證子線程被cancel后,能夠執行線程清理函數
void clean1(void* p)
{
        printf("I am clean func %d\n",(int)p);
}

void* thread(void* p)
{
        pthread_cleanup_push(clean1,(void*)1);
        printf("I am child\n");
        char buf[128]={0};
        read(0,buf,sizeof(buf));
        printf("after read\n");
        pthread_exit((void*)5);
        pthread_cleanup_pop(0);
}

int main()
{
        pthread_t pth_id;
        pthread_create(&pth_id,NULL,thread,NULL);
        int ret;
        ret=pthread_cancel(pth_id);
        if(ret!=0)
        {
                printf("pthread_cancel failed ret=%d\n",ret);
                return -1;
        }
        int i;
        ret=pthread_join(pth_id,(void**)&i); 
        if(ret!=0)
        {
                printf("pthread_join failed ret=%d\n",ret);
                return -1;
        }
        printf("main thread i =%d\n",(int)i);
        return 0;
}


pthread_cleanup2.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void clean1(void* p)
{
        printf("I am clean func %d\n",(int)p);
}

void* thread(void* p)
{
        pthread_cleanup_push(clean1,(void*)1);
        pthread_cleanup_push(clean1,(void*)2);
        printf("I am child\n");
        char buf[128]={0};
        read(0,buf,sizeof(buf));
        printf("after read\n");
        pthread_exit((void*)5);
        pthread_cleanup_pop(0);
        pthread_cleanup_pop(0);
}

int main()
{
        pthread_t pth_id;
        pthread_create(&pth_id,NULL,thread,NULL);
        int ret;
        ret=pthread_cancel(pth_id);
        if(ret!=0)
        {
                printf("pthread_cancel failed ret=%d\n",ret);
                return -1;
        }
        void* p;
        ret=pthread_join(pth_id,&p);  
        if(ret!=0)
        {
                printf("pthread_join failed ret=%d\n",ret);
                return -1;
        }
        printf("main thread p=%d\n",(int)p);
        return 0;
}



//子線程動態申請空間,被其他線程取消,可以調用清理函數清理
pthread_clean_malloc.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

void clean1(void* p)
{
        printf("I am clean,p =%s\n",(char*)p);
        free(p);   //由清理函數進行堆內存free
}

void* thread(void* p)
{
        p=malloc(20);
        pthread_cleanup_push(clean1,p);
        printf("I am child\n");
        strcpy((char*)p,"hello");
        char buf[128]={0};
        read(0,buf,sizeof(buf));
        printf("after read\n");
        pthread_exit((void*)5);  //如果沒有這一句,要執行清理函數就要把下面參數改為非0
        pthread_cleanup_pop(0);
}

int main()
{
        pthread_t pth_id;
        pthread_create(&pth_id,NULL,thread,NULL);
        int ret;
        sleep(2);
        ret=pthread_cancel(pth_id);
        if(ret!=0)
        {
                printf("pthread_cancel failed ret=%d\n",ret);
                return -1;
        }
        void* q;
        ret=pthread_join(pth_id,&q);  //不獲取子線程的返回值
        if(ret!=0)
        {
                printf("pthread_join failed ret=%d\n",ret);
                return -1;
        }
        printf("main thread q=%d\n",(int)q);
        return 0;
}


// 第一個結果是被中途取消了的,退出值-1,;第二個結果是線程運行到pthread_exit((void*)5)退出,退出值就是5








免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM