APUE讀書筆記-11線程(3)

5兰英、線程終止

當進程的任何一個線程調(diào)用 exit , _exit 或者 _Exit 的時候展融,整個進程都會被終止寞射。類似地渔工,當信號的默認處理動作是終止進程的時候,給一個線程發(fā)送信號會導(dǎo)致整個進程的終止桥温。我們后面會討論線程和信號的交互引矩。

正常地終止一個線程而不終止整個進程,有三個方法:

  1. 線程從它的起始函數(shù)中正常地返回侵浸。這時候旺韭,線程的退出碼就是返回值。

  2. 線程被同一個進程中的其他線程取消掏觉。

  3. 線程調(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_createpthread_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 :

  1. 調(diào)用 pthread_exit
  2. 響應(yīng)取消請求
  3. 使用非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)的線程。

譯者注

原文參考

參考: APUE2/ch11lev1sec5.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末髓帽,一起剝皮案震驚了整個濱河市菠赚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌郑藏,老刑警劉巖衡查,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異必盖,居然都是意外死亡拌牲,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門歌粥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塌忽,“玉大人,你說我怎么就攤上這事失驶⊥辆樱” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵嬉探,是天一觀的道長擦耀。 經(jīng)常有香客問我,道長甲馋,這世上最難降的妖魔是什么埂奈? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任迄损,我火速辦了婚禮定躏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己痊远,他們只是感情好垮抗,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著碧聪,像睡著了一般冒版。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逞姿,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天辞嗡,我揣著相機與錄音,去河邊找鬼滞造。 笑死续室,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的谒养。 我是一名探鬼主播挺狰,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼买窟!你這毒婦竟也來了丰泊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤始绍,失蹤者是張志新(化名)和其女友劉穎瞳购,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亏推,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡苛败,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了径簿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罢屈。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖篇亭,靈堂內(nèi)的尸體忽然破棺而出缠捌,到底是詐尸還是另有隱情,我是刑警寧澤译蒂,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布曼月,位于F島的核電站,受9級特大地震影響柔昼,放射性物質(zhì)發(fā)生泄漏哑芹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一捕透、第九天 我趴在偏房一處隱蔽的房頂上張望聪姿。 院中可真熱鬧碴萧,春花似錦、人聲如沸末购。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盟榴。三九已至曹质,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間擎场,已是汗流浹背羽德。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留迅办,地道東北人玩般。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像礼饱,于是被迫代替她去往敵國和親坏为。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354