【線程退出】linux線程退出的幾個函數


一、線程退出

1、pthread_kill

(1)引用
#include <pthread.h>
#include<signal.h>
(2)函數原型
int pthread_kill(pthread_t thread, int sig);
(3)參數
  • thread:線程ID
  • sig:sig信號
(4)返回值
  • 0:調用成功。

  •  ESRCH:線程不存在。。

  • EINVAL:信號不合法
(4)作用

  向指定的線程傳遞sig信號。

5)注意
  別被名字嚇到,pthread_kill可不是kill,而是向線程發送signal。還記得signal嗎,大部分signal的默認動作是終止進程的運行,所以,我們才要用signal()去抓信號並加上處理函數。向指定ID的線程發送sig信號,如果線程代碼內不做處理,則按照信號默認的行為影響整個進程,也就是說,如果你給一個線程發送了SIGQUIT,但線程卻沒有實現signal處理函數,則整個進程退出。如果要獲得正確的行為,就需要在線程內實現signal(SIGKILL,sig_handler)了。
(6)程序實例
//使用pthread_kill函數檢測一個線程是否還活着
/******************************* pthread_kill.c *******************************/
#include<stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>


void *func1()/*1秒鍾之后退出*/
{
    sleep(1);
    printf("線程1(ID:0x%x)退出。\n",(unsigned int)pthread_self());
    pthread_exit((void *)0);
}

void *func2()/*5秒鍾之后退出*/
{
    sleep(5);
    printf("線程2(ID:0x%x)退出。\n",(unsigned int)pthread_self());
    pthread_exit((void *)0);
}

void test_pthread(pthread_t tid) /*pthread_kill的返回值:成功(0) 線程不存在(ESRCH) 信號不合法(EINVAL)*/
{
    int pthread_kill_err;
    pthread_kill_err = pthread_kill(tid,0);

    if(pthread_kill_err == ESRCH)
        printf("ID為0x%x的線程不存在或者已經退出。\n",(unsigned int)tid);
    else if(pthread_kill_err == EINVAL)
        printf("發送信號非法。\n");
    else
        printf("ID為0x%x的線程目前仍然存活。\n",(unsigned int)tid);
}

int main()
{
    int ret;
    pthread_t tid1,tid2;
    
    pthread_create(&tid1,NULL,func1,NULL);
    pthread_create(&tid2,NULL,func2,NULL);
    
    sleep(3);/*創建兩個進程3秒鍾之后,分別測試一下它們是否還活着*/
    
    test_pthread(tid1);/*測試ID為tid1的線程是否存在*/
    test_pthread(tid2);/*測試ID為tid2的線程是否存在*/

    exit(0);
}

  

//kill給線程發信號

#include <pthread.h>  
#include <stdio.h>  
#include <sys/signal.h>  
  
#define NUMTHREADS 3  
void sighand(int signo);  
  
void *threadfunc(void *parm)  
{  
    pthread_t             tid = pthread_self();  
    int                   rc;  
  
    printf("Thread %u entered/n", tid);  
    rc = sleep(30); /* 若有信號中斷則返回剩余秒數 */  
    printf("Thread %u did not get expected results! rc=%d/n", tid, rc);  
    return NULL;  
}  
  
void *threadmasked(void *parm)  
{  
    pthread_t             tid = pthread_self();  
    sigset_t              mask;  
    int                   rc;  
  
    printf("Masked thread %lu entered/n", tid);  
  
    sigfillset(&mask); /* 將所有信號加入mask信號集 */  
  
    /* 向當前的信號掩碼中添加mask信號集 */  
    rc = pthread_sigmask(SIG_BLOCK, &mask, NULL);  
    if (rc != 0)  
    {  
        printf("%d, %s/n", rc, strerror(rc));  
        return NULL;  
    }  
  
    rc = sleep(15);  
    if (rc != 0)  
    {  
        printf("Masked thread %lu did not get expected results! rc=%d /n", tid, rc);  
        return NULL;  
    }  
    printf("Masked thread %lu completed masked work/n", tid);  
    return NULL;  
}  
  
int main(int argc, char **argv)  
{  
    int                     rc;  
    int                     i;  
    struct sigaction        actions;  
    pthread_t               threads[NUMTHREADS];  
    pthread_t               maskedthreads[NUMTHREADS];  
  
    printf("Enter Testcase - %s/n", argv[0]);  
    printf("Set up the alarm handler for the process/n");  
  
    memset(&actions, 0, sizeof(actions));  
    sigemptyset(&actions.sa_mask); /* 將參數set信號集初始化並清空 */  
    actions.sa_flags = 0;  
    actions.sa_handler = sighand;  
  
    /* 設置SIGALRM的處理函數 */  
    rc = sigaction(SIGALRM,&actions,NULL);  
  
    printf("Create masked and unmasked threads/n");  
    for(i=0; i<NUMTHREADS; ++i)  
    {  
        rc = pthread_create(&threads[i], NULL, threadfunc, NULL);  
        if (rc != 0)  
        {  
            printf("%d, %s/n", rc, strerror(rc));  
            return -1;  
        }  
  
        rc = pthread_create(&maskedthreads[i], NULL, threadmasked, NULL);  
        if (rc != 0)  
        {  
            printf("%d, %s/n", rc, strerror(rc));  
            return -1;  
        }  
    }  
  
    sleep(3);  
    printf("Send a signal to masked and unmasked threads/n");  
  
     /* 向線程發送SIGALRM信號 */  
    for(i=0; i<NUMTHREADS; ++i)  
    {  
        rc = pthread_kill(threads[i], SIGALRM);  
        rc = pthread_kill(maskedthreads[i], SIGALRM);  
    }  
  
    printf("Wait for masked and unmasked threads to complete/n");  
    for(i=0; i<NUMTHREADS; ++i) {  
        rc = pthread_join(threads[i], NULL);  
        rc = pthread_join(maskedthreads[i], NULL);  
    }  
  
    printf("Main completed/n");  
    return 0;  
}  
  
void sighand(int signo)  
{  
    pthread_t             tid = pthread_self();  
  
    printf("Thread %lu in signal handler/n", tid);  
    return;  
}  

2、pthread_exit

(1)函數原型
void pthread_exit(void* retval);

(2)參數

  pthread_exit函數唯一的參數value_ptr是函數的返回代碼,只要pthread_join中的第二個參數value_ptr不是NULL,這個值將被傳遞給value_ptr。

(3)作用

線程通過調用pthread_exit函數終止執行,就如同進程在結束時調用exit函數一樣。這個函數的作用是,終止調用它的線程並返回一個指向某個對象的指針。

(4)用法

  • 在線程中調用pthread_exit退出線程,在主線程中嗲用pthread_join函數阻塞等待結束並釋放資源,pthread_exit的參數會傳給pthread_join.
  • 設置線程為分離屬性,線程中調用pthread_exit退出線程后系統自動釋放資源。
  線程可以調用pthread_exit終止自己,有兩種情況需要注意:
  • 在主線程中,如果從main函數返回或是調用了exit函數退出主線程,則整個進程將終止,此時進程中有線程也將終止,因此在主線程中不能過早地從main函數返回;
  • 如果主線程調用pthread_exit函數,則僅僅是主線程消亡進程不會結束,進程內的其他線程也不會終止,直到所有線程結束,進程才會結束

3、pthread_join

  函數pthread_join用來等待一個線程的結束,線程間同步的操作。 

  pthread_join()函數,以阻塞的方式等待thread指定的線程結束。當函數返回時,被等待線程的資源被收回。如果線程已經結束,那么該函數會立即返回。並且thread指定的線程必須是joinable(合並)的。

(1)函數原型
int pthread_join( pthread_t  thread, void * * value_ptr );
(2)參數
thread:線程ID
retval:用戶定義的指針,用來存儲被等待線程的返回值。
(3)返回值
0代表成功。 失敗,返回的則是錯誤號。
  (4)解釋
  • 代碼中如果沒有pthread_join主線程會很快結束從而使整個進程結束,從而使創建的線程沒有機會開始執行就結束了。加入pthread_join后,主線程會一直等待直到等待的線程結束自己才結束,使創建的線程有機會執行。
  • 通過pthread_join()函數來使主線程阻塞等待其他線程退出,這樣主線程可以清理其他線程的環境。但是還有一些線程,更喜歡自己來清理退出的狀態,他們也不願意主線程調用pthread_join來等待他們。我們將這一類線程的屬性稱為detached(分離的)。如果我們在調用 pthread_create()函數的時候將屬性設置為NULL,則表明我們希望所創建的線程采用默認的屬性,也就是jionable(此時不是detached)。
 
4、pthread_cancel
(1)作用
  用於取消一個函數,它通常需要被取消線程的配合。默認情況(延遲取消),它就是給pthread設置取消標志, pthread線程在很多時候會查看自己是否有取消請求。如果有就主動退出, 這些查看是否有取消的地方稱為取消點。
當然,線程也不是被動的被別人結束。它可以通過設置自身的屬性來決定如何結束。
線程的被動結束分為兩種:
  • 異步終結:當其他線程調用pthread_cancel的時候,線程就立刻被結束。
  • 同步終結:同步終結則不會立刻終結,它會繼續運行,直到到達下一個結束點(cancellation point)。當一個線程被按照默認的創建方式創建,那么它的屬性是同步終結。

  若是在整個程序退出時,要終止各個線程,應該在成功發送 CANCEL 指令后,使用 pthread_join 函數,等待指定的線程已經完全退出以后,再繼續執行;否則,很容易產生 “段錯誤”。

(2)函數原型
#include<pthread.h>
int pthread_cancel(pthread_t thread)
(3)返回值
成功則返回0,否則為非0值。發送成功並不意味着thread會終止。
(4)用法
若是在整個程序退出時,要終止各個線程,應該在成功發送 CANCEL 指令后,使用 pthread_join 函數,等待指定的線程已經完全退出以后,再繼續執行;否則,很容易產生 “段錯誤”。
int pthread_setcancelstate(int state, int *oldstate)
設置本線程對Cancel信號的反應,state有兩種值:
  • PTHREAD_CANCEL_ENABLE(缺省):收到信號后設為CANCLED狀態
  • PTHREAD_CANCEL_DISABLE:忽略CANCEL信號繼續運行;

old_state如果不為 NULL則存入原來的Cancel狀態以便恢復。

int pthread_setcanceltype(int type, int *oldtype)
設置本線程取消動作的執行時機,
  type由兩種取值(僅當Cancel狀態為Enable時有效):
  • PTHREAD_CANCEL_DEFERRED:表示收到信號后繼續運行至下一個取消點再退出
  • PTHREAD_CANCEL_ASYNCHRONOUS:立即執行取消動作(退出)

  oldtype如果不為NULL則存入原來的取消動作類型值。

  此函數應該在線程開始時執行,若線程內部有任何資源申請等操作,應該選擇 PTHREAD_CANCEL_DEFERRED 的設定,然后在退出點(pthread_testcancel 用於定義退出點)進行線程退出。
void pthread_testcancel(void)
檢查本線程是否處於Canceld狀態,如果是,則進行取消動作,否則直接返回。 此函數在線程內執行,執行的位置就是線程退出的位置,在執行此函數以前,線程內部的相關資源申請一定要釋放掉,他很容易造成內存泄露

(5)程序實例

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

void* func(void *arg)
{
   pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);           //允許退出線程
   pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,   NULL);   //設置立即取消
   while(1);
  return NULL;
}

int main(int argc,char *argv[])
{
  pthread_t thrd;
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

  if(pthread_create(&thrd,&attr,func,NULL))
  {
    perror( "pthread_create   error ");
    exit(EXIT_FAILURE);
  }

  if(!pthread_cancel(thrd))
  {
    printf("pthread_cancel OK\n");
  }
  sleep(10);
  return 0;
}

5、exit()

如:exit(EXIT_SUCCESS),是進程退出,如果在線程函數中調用exit,那改線程的進程也就掛了,會導致該線程所在進程的其他線程也掛掉,比較嚴重。

6、return

  return是函數返回,不一定是線程函數,只有線程函數return,線程才會退出
對比說明:
  • pthread_join一般是主線程來調用,用來等待子線程退出,因為是等待,所以是阻塞的,一般主線程會依次join所有它創建的子線程。
  • pthread_exit一般是子線程調用,用來結束當前線程。子線程可以通過pthread_exit傳遞一個返回值,而主線程通過pthread_join獲得該返回值,從而判斷該子線程的退出是正常還是異常。

 二、資源清理

  一旦又處於掛起狀態的取消請求(即加鎖之后,解鎖之前),線程在執行到取消點時如果只是草草收場,這會將共享變量以及pthreads對象(例如互斥量)置於一種不一致狀態,可能導致進程中其他線程產生錯誤結果、死鎖,甚至造成程序崩潰。為避免這一問題:

使用清理函數pthread_cleanup_push()pthread_cleanup_pop()來處理,這兩個函數必需成對出現,不然會編譯錯誤。

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

最近常出現的情形時資源獨占鎖的使用:

  線程為了訪問臨界共享資源而為其加上鎖,但在訪問過程唄外界取消,或者發生了中斷,則該臨界資源將永遠處於鎖定狀態得不到釋放。外界取消操作是不可見的,因此的確需要一個機制來簡化用於資源釋放的編程。

在POSIX線程API中提供了一個pthread_clean_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)

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 前,被 pthread_cancel 取消
  • 線程還未執行 pthread_cleanup_pop 前,主動執行 pthread_exit 終止
  • 線程執行 pthread_cleanup_pop,且 pthread_cleanup_pop 的參數不為 0.

注意:如果線程還未執行 pthread_cleanup_pop 前通過 return 返回,是不會執行清理函數的。

void routine()函數可參照如下定義:

void *cleanup(void* p)
{
        free(p);
        printf("清理函數\n");
}

 線程主動清理過程的嚴謹寫法:

void thread_fun(void*p)
{      p=malloc(20);
        pthread_cleanup_push(cleanup,p);
        printf("子線程\n");
        sleep(1);            //系統調用,用來響應pthread_cancel函數
        printf("是否殺死了線程\n");  //如果線程在上一句被殺死,這一句不會被打印
        pthread_exit(NULL);      //不管線程是否被殺死,這一句都會檢測清理函數,並執行
        pthread_clean_pop(1);
}

 注意:在子線程中如果申請了單獨的堆空間,不應用free直接清理;因為假如在線程中直接free,如果,在free之后線程被取消,清理函數被執行,則會出現重復free的情況。  

情況如下:

void thread_fun(void*p)
{
    p=malloc(20);
       pthread_cleanup_push(cleanup,p);
       printf("子線程\n");
       sleep(1);            //系統調用,用來響應pthread_cancel函數
       printf("是否殺死了線程\n");  //如果線程在上一句被殺死,這一句不會被打印
       free(p);      //不管線程是否被殺死,這一句都會檢測清理函數,並執行<br>
       //加入函數在此處被cancel ,可能會出現重復free
     sleep(1);//在此處系統調用,響應pthread_cancel(),會執行清理函數.            
       return NULL;
      pthread_clean_pop(1);//由於上一句return,所以這一句不執行,即清理函數不會執行
}

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),以完成解鎖動作。


以下是使用方法:
pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
pthread_mutex_lock(&mut);
.......
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);
 }    
補充:
  在線程宿主函數中主動調用return,如果return語句包含在pthread_cleanup_push()/pthread_cleanup_pop()對中,則不會引起清理函數的執行,反而會導致segment fault。


免責聲明!

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



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