5兰英、線程終止
當進程的任何一個線程調(diào)用 exit
, _exit
或者 _Exit
的時候展融,整個進程都會被終止寞射。類似地渔工,當信號的默認處理動作是終止進程的時候,給一個線程發(fā)送信號會導(dǎo)致整個進程的終止桥温。我們后面會討論線程和信號的交互引矩。
正常地終止一個線程而不終止整個進程,有三個方法:
線程從它的起始函數(shù)中正常地返回侵浸。這時候旺韭,線程的退出碼就是返回值。
線程被同一個進程中的其他線程取消掏觉。
-
線程調(diào)用pthread_exit.
include <pthread.h>
void pthread_exit(void *rval_ptr);
參數(shù) rval_ptr
是一個無類型的指針区端,它可以被進程中的其他線程通過調(diào)用 pthread_join
來使用。
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
如果成功返回0澳腹,如果失敗织盼,返回錯誤號碼。
調(diào)用這個函數(shù)的線程將會阻塞酱塔,直到這個函數(shù)所指定的線程調(diào)用了 pthread_exit
,或者從其主函數(shù)中返回沥邻,或者被取消。如果線程從它的主函數(shù)中返回延旧, rval_prt
將會包含相應(yīng)的返回碼;如果線程被取消谋国, rval_ptr
指向的內(nèi)存地址將會被設(shè)置為 PTHREAD_CANCELED
.
調(diào)用 pthread_join
會自動地把線程置于 detached
狀態(tài)槽地,以便恢復(fù)線程的資源(稍后會講到)迁沫。如果線程已經(jīng)是 detached
狀態(tài)了,那么 pthread_join
會失敗并且返回 EINVAL
.
如果我們對線程的返回值不感興趣捌蚊,那么我們可以把rval_ptr設(shè)置為空集畅,這樣會等待指定的線程但是不獲取線程的退出狀態(tài)。
舉例
void *thr_fn1(void *arg)
{
printf("thread 1 returning\n");
return((void *)1);
}
void *thr_fn2(void *arg)
{
printf("thread 2 exiting\n");
pthread_exit((void *)2);
}
int main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
err = pthread_create(&tid1, NULL, thr_fn1, NULL);
if (err != 0)
err_quit("can't create thread 1: %s\n", strerror(err));//一個出了錯就退出程序的函數(shù).
err = pthread_create(&tid2, NULL, thr_fn2, NULL);
if (err != 0)
err_quit("can't create thread 2: %s\n", strerror(err));
err = pthread_join(tid1, &tret);
if (err != 0)
err_quit("can't join with thread 1: %s\n", strerror(err));
printf("thread 1 exit code %d\n", (int)tret);
err = pthread_join(tid2, &tret);
if (err != 0)
err_quit("can't join with thread 2: %s\n", strerror(err));
printf("thread 2 exit code %d\n", (int)tret);
exit(0);
}
運行如下:
$ ./a.out
thread 1 returning
thread 2 exiting
thread 1 exit code 1
thread 2 exit code 2
可以看出缅糟,一個線程如果從 start
函數(shù)中退出挺智,或者調(diào)用 pthread_exit
退出,那么其他的進程可以通過 pthread_join
來獲取進程的結(jié)束狀態(tài)窗宦。
我們可以給 pthread_create
和 pthread_exit
傳遞一個無類型的指針赦颇,這樣指針可以指向復(fù)雜的結(jié)構(gòu)二鳄,包含更多得信息。需要注意的是當線程結(jié)束的時候媒怯,指針指向的位置應(yīng)該還是合法的订讼。如果指針指向的位置是在棧上面分配的,那么當線程結(jié)束之后扇苞,棧內(nèi)容就不確定了欺殿。而調(diào)用 pthread_join
的調(diào)用者卻使用了剛才棧所在地址的內(nèi)容。
線程可以通過調(diào)用 pthread_cancel
函數(shù)請求同一個進程中的其他線程被取消鳖敷。
#include <pthread.h>
int pthread_cancel(pthread_t tid);
返回值:0表示成功脖苏,錯誤碼表示失敗。
默認情況下 pthread_cancel
調(diào)用會導(dǎo)致 tid
指定的線程表現(xiàn)的像是自己調(diào)用具有 PTHREAD_CANCELED
參數(shù)的 pthread_exit
一樣定踱。線程也可以選擇忽略其他線程對它的取消棍潘,以及選擇如何被取消以后會講到。然而 pthread_cancel
不會等待線程結(jié)束崖媚,它只是做一個請求蜒谤。
線程可以設(shè)置退出時候調(diào)用的函數(shù),這個和進程使用 atexit
函數(shù)設(shè)置進程退出時候調(diào)用得函數(shù)類似至扰。這些函數(shù)叫做“線程清理函數(shù)”鳍徽,可以為線程設(shè)置多個清理函數(shù),這些清理函數(shù)被記錄在棧中敢课,這也意味這這些函數(shù)的調(diào)用次序和它們被注冊的次序相反阶祭。
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
當線程執(zhí)行如下動作的時候, pthread_cleanup_push
會調(diào)度清理函數(shù)直秆,函數(shù)由 rtn
指向并且參數(shù)是 arg
:
- 調(diào)用
pthread_exit
- 響應(yīng)取消請求
- 使用非0的
execute
參數(shù)調(diào)用pthread_cleanup_pop
.
當 pthread_cleanup_pop
參數(shù)為0的時候濒募,不會調(diào)用清理函數(shù),這個時候會把最后一次調(diào)用 pthread_cleanup_push
的函數(shù)去掉圾结。
這些函數(shù)的使用限制就是它們是使用宏實現(xiàn)的瑰剃,它們必須在一個線程的同一個作用域內(nèi)成對匹配使用, pthread_cleanup_push
宏包含是一個 {
, pthread_cleanup_pop
宏包含一個 }
筝野。
舉例:
void cleanup(void *arg)
{
printf("cleanup: %s\n", (char *)arg);
}
void * thr_fn1(void *arg)
{
printf("thread 1 start\n");
pthread_cleanup_push(cleanup, "thread 1 first handler");
pthread_cleanup_push(cleanup, "thread 1 second handler");
printf("thread 1 push complete\n");
if (arg)
return((void *)1);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return((void *)1);
}
void * thr_fn2(void *arg)
{
printf("thread 2 start\n");
pthread_cleanup_push(cleanup, "thread 2 first handler");
pthread_cleanup_push(cleanup, "thread 2 second handler");
printf("thread 2 push complete\n");
if (arg)
pthread_exit((void *)2);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_exit((void *)2);
}
int main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
if (err != 0)
err_quit("can't create thread 1: %s\n", strerror(err));
err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
if (err != 0)
err_quit("can't create thread 2: %s\n", strerror(err));
err = pthread_join(tid1, &tret);
if (err != 0)
err_quit("can't join with thread 1: %s\n", strerror(err));
printf("thread 1 exit code %d\n", (int)tret);
err = pthread_join(tid2, &tret);
if (err != 0)
err_quit("can't join with thread 2: %s\n", strerror(err));
printf("thread 2 exit code %d\n", (int)tret);
exit(0);
}
上面的例子展示了如何使用線程的清理函數(shù)晌姚。需要注意的是盡管我們沒有打算給線程的啟動函數(shù)傳遞非0參數(shù),我們還是需要調(diào)用 pthread_cleanup_pop
函數(shù)來匹配 pthread_cleanup_push
函數(shù)歇竟,否則程序無法編譯通過挥唠。
運行這個程序的輸出是:
$ ./a.out
thread 1 start
thread 1 push complete
thread 2 start
thread 2 push complete
cleanup: thread 2 second handler
cleanup: thread 2 first handler
thread 1 exit code 1
thread 2 exit code 2
從輸出中我們可以看到,兩個線程都正常地啟動和退出了焕议,但是只有第二個線程調(diào)用了清理函數(shù)宝磨。因此,如果線程是通過從啟動函數(shù)中正常返回而終止的話,就不會執(zhí)行清理函數(shù)唤锉。并且我們也應(yīng)該注意啟動函數(shù)的調(diào)用次序和它們被安裝的次序是相反的世囊。
實際線程和進程有許多類似的函數(shù),下表給出了這個對比窿祥。
進程和線程相關(guān)函數(shù)的對比
+-------------------------------------------------------------------------------------------------------+
| Process primitive | Thread primitive | Description |
|-------------------+---------------------+-------------------------------------------------------------|
| fork | pthread_create | create a new flow of control |
|-------------------+---------------------+-------------------------------------------------------------|
| exit | pthread_exit | exit from an existing flow of control |
|-------------------+---------------------+-------------------------------------------------------------|
| waitpid | pthread_join | get exit status from flow of control |
|-------------------+---------------------+-------------------------------------------------------------|
| atexit | pthread_cancel_push | register function to be called at exit from flow of control |
|-------------------+---------------------+-------------------------------------------------------------|
| getpid | pthread_self | get ID for flow of control |
|-------------------+---------------------+-------------------------------------------------------------|
| abort | pthread_cancel | request abnormal termination of flow of control |
+-------------------------------------------------------------------------------------------------------+
默認來說茸习,一個線程的終止狀態(tài)會一直保留到 pthread_join
被調(diào)用。一個終止的線程所占的內(nèi)存會在 detached
的時候立即被回收壁肋,當一個線程被 detached
的時候号胚,不能使用 pthread_join
函數(shù)等待獲取它的終止狀態(tài)。對一個 detached
的線程調(diào)用 pthread_join
會失敗浸遗,并且返回 EINVAL
猫胁。我們可以使用 pthread_detach
來將一個線程 detach
.
#include <pthread.h>
int pthread_detach(pthread_t tid);
返回:如果成功返回0,如果失敗返回錯誤編號跛锌。
后面我們可以看到弃秆,我們可以通過修改傳遞給 pthread_create
的線程屬性參數(shù)來建立一個開始就處于 detached
狀態(tài)的線程。