一、線程退出
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)注意
(6)程序實例
/******************************* 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
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退出線程后系統自動釋放資源。
- 在主線程中,如果從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)。
(1)作用
- 異步終結:當其他線程調用pthread_cancel的時候,線程就立刻被結束。
- 同步終結:同步終結則不會立刻終結,它會繼續運行,直到到達下一個結束點(cancellation point)。當一個線程被按照默認的創建方式創建,那么它的屬性是同步終結。
若是在整個程序退出時,要終止各個線程,應該在成功發送 CANCEL 指令后,使用 pthread_join 函數,等待指定的線程已經完全退出以后,再繼續執行;否則,很容易產生 “段錯誤”。
(2)函數原型
#include<pthread.h>
int pthread_cancel(pthread_t thread)
(3)返回值
成功則返回0,否則為非0值。發送成功並不意味着thread會終止。
(4)用法
int pthread_setcancelstate(int state, int *oldstate)
- PTHREAD_CANCEL_ENABLE(缺省):收到信號后設為CANCLED狀態
- PTHREAD_CANCEL_DISABLE:忽略CANCEL信號繼續運行;
old_state如果不為 NULL則存入原來的Cancel狀態以便恢復。
int pthread_setcanceltype(int type, int *oldtype)
- PTHREAD_CANCEL_DEFERRED:表示收到信號后繼續運行至下一個取消點再退出
- PTHREAD_CANCEL_ASYNCHRONOUS:立即執行取消動作(退出)
oldtype如果不為NULL則存入原來的取消動作類型值。
void pthread_testcancel(void)
(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()
6、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。