第4篇 Linux多線程--Join vs Detach

前面一篇我們已經(jīng)談及主線程和子線程之間的關(guān)系,以及線程在運(yùn)行時的線程狀態(tài),本篇我會討論到如何優(yōu)雅地連接線程溃蔫,并且通過一個具體的示例來結(jié)合前一篇所說的線程狀態(tài)來分析不合理使用連接線程帶來的負(fù)面影響姊扔。

線程的連接

我們通過man命令查看一下pthread_join的文檔

ss18.png

int pthread_join(pthread_t thread, void **retval);

  • 參數(shù)thread 就是傳入線程的ID
  • 參數(shù)retval 就是傳入線程的返回碼盔夜,如果線程被取消怎燥,那么rval被重置為PTHREAD_CANCELED, 如果調(diào)用成功瘫筐,返回0,失敗就返回大于0的整數(shù)。

再進(jìn)一步之前铐姚,我們需要了解一下前面沒有談及的線程屬性--分離

  • 默認(rèn)情況下,pthread_create創(chuàng)建的進(jìn)程是非分離的,分離是指一個運(yùn)行時的線程的一個特定屬性肛捍,只是告知系統(tǒng)內(nèi)核該線程結(jié)束時隐绵,其使用的資源可以回收,其中包括釋放所有該線程結(jié)束時未釋放的系統(tǒng)的資源(包括返回值的內(nèi)存空間,堆拙毫,棧依许,寄存器等內(nèi)存空間)。
  • 一個沒有被分離的線程在結(jié)束時缀蹄,系統(tǒng)內(nèi)核會保留它的虛擬內(nèi)存峭跳,當(dāng)中包括它們的堆和棧,寄存器(這種線程,通常叫僵尸線程缺前,那么該僵尸線程的宿主進(jìn)程蛀醉,自然就成為僵尸進(jìn)程)
    還有其他線程屬性,請參考這個文檔

pthread_join調(diào)用注意事項

  • 調(diào)用該系統(tǒng)調(diào)用會使傳入的線程擁有分離屬性衅码,如果該線程已經(jīng)處于分離的狀態(tài)拯刁,那么調(diào)用就會失敗。
  • 調(diào)用該系統(tǒng)調(diào)用的線程會一直阻塞逝段,直到指定的線程(用線程ID來標(biāo)識)調(diào)用pthread_exit垛玻,從啟動的線程回調(diào)函數(shù)中返回或者調(diào)用pthread_cancel(傳入該線程的ID)

下面的例子是一個非常好的例子割捅,非常形象地解析Linux內(nèi)核的線程管理機(jī)制的,首先我們創(chuàng)建了一個代表非負(fù)整數(shù)序列的結(jié)構(gòu)體Seque帚桩,然后在1到200分成三個長度不同的數(shù)據(jù)區(qū)間亿驾,分別傳遞給三個線程。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include<string.h>

typedef struct{
    unsigned long begin;
    unsigned long end;
    unsigned long sum;
} Sequen;

void* calc(void* args){

    Sequen* se=(Sequen*)args;
    unsigned long beg=se->begin;
    unsigned long end=se->end;
    
    pthread_t tid=pthread_self();

    //計算奇數(shù)和
    for(unsigned long i=beg;i<=end;i+=2){
        se->sum+=i; 
    }
    printf("子線程0x%lx ->計算結(jié)果:%lu\n",tid,se->sum);
    return NULL;
}

int main(int argc,char* argv){

    pthread_t tids[3];

    Sequen seq[3]={
        {1,50,0},
        {51,100,0},
        {101,200,0}
    };

    int err;
    for(int i=0;i<3;i++){
        printf("創(chuàng)建地第%d個子線程\n",i+1);
        err=pthread_create(&tids[i],NULL,calc,&seq[i]);
        if (err){
            printf("創(chuàng)建線程失敗!!\n");
            continue;
        }
        pthread_join(tids[i],NULL);
    }

    printf("主線程計算結(jié)果:%ld\n",seq[0].sum+seq[1].sum+seq[2].sum);
}

輸出結(jié)果:你覺得很奇怪是嗎账嚎?為什么三個子線程的ID是一樣的呢颊乘?

我們分析一下這里的三個線程發(fā)生了什么事情,首先我們在for循環(huán)中分別使用pthread_create創(chuàng)先后創(chuàng)建了三個進(jìn)程(注意:字眼是“先后”代表是有順序)

  1. 第一個被創(chuàng)建的線程醉锄,系統(tǒng)從線程ID的編號空間中分配一個線程ID(十六進(jìn)制)0x7f1657e2e700給tids[0],隨后立即將該子線程傳遞給pthread_join處理,此時第一個線程創(chuàng)建之后乏悄,但未被pthread_join處理完之前,該線程的狀態(tài)是“運(yùn)行”了恳不。注意,這個phread_join是主線程調(diào)用的,因此主線程進(jìn)入“阻塞”狀態(tài),此時主線程只能等第一個子線程結(jié)束并且什么事都做不了檩小,因此也無法創(chuàng)建剩下的兩個子線程。
  2. 當(dāng)?shù)谝粋€線程終止后,主線程從“阻塞”回到“運(yùn)行”狀態(tài)烟勋,而且在第一個子線程結(jié)束后规求,系統(tǒng)內(nèi)核回收了第一個子線程ID:0x7f1657e2e700,并將該ID再“轉(zhuǎn)租”給第二個子線程。因此第一個子線程和第二個子線程的線程ID是一樣的卵惦。當(dāng)主線程再次調(diào)用pthread_join后阻肿,那么主線程再次從“運(yùn)行”狀態(tài)切換到“阻塞”狀態(tài),一直等到第二個子線程執(zhí)行完畢,才能接著創(chuàng)建第三個子線程沮尿。
  3. 同理丛塌,第三個子線程也和前面兩個步驟的分析一樣。

你因為這個分析不合理嗎畜疾?我也查閱了gnu官網(wǎng)的相關(guān)文檔說明https://www.gnu.org/software/libc/manual/html_node/Process-Identification.html

ss18.png

中文翻譯:

在Linux上赴邻,由pthread_create創(chuàng)建的線程也會收到線程ID。 初始(主)線程的線程ID與整個進(jìn)程的進(jìn)程ID相同啡捶。 隨后創(chuàng)建的線程的線程ID是不同的姥敛。 它們是從與進(jìn)程ID相同的編號空間分配的。 有時瞎暑,進(jìn)程ID和線程ID也統(tǒng)稱為任務(wù)ID彤敛。 與進(jìn)程相比,線程從不顯式等待了赌,因此線程ID在線程退出或取消后立即可以重用墨榄。 即使對于可連接線程,也不僅僅是分離線程揍拆,都是如此渠概。 線程被分配給線程組。 在Linux上運(yùn)行的GNU C庫實現(xiàn)中,進(jìn)程ID是進(jìn)程中所有線程的線程組ID播揪。

phread_join的副作用

OK贮喧,phread_join調(diào)用雖然能夠使改變目標(biāo)子線程的線程屬性,但它明顯的副作用是調(diào)用它的函數(shù)上下文所處的線程處于阻塞狀態(tài)猪狈,調(diào)用phread_join的線程無法執(zhí)行接下來的其他指令箱沦。

試問上面的示例跟一個單線程的同步調(diào)用沒什么區(qū)別!~因為主線程的間隔性的堵塞,令到子線程沒辦法連續(xù)創(chuàng)建和并發(fā)運(yùn)行雇庙。因此谓形,我們使用在多線程編程中,要謹(jǐn)慎使用phread_join調(diào)用疆前,我們應(yīng)該結(jié)合我們具體的上下問邏輯來合理使用phread_join系統(tǒng)調(diào)用.

Join vs Detach

這是本篇的主題寒跳,我們之前本篇開頭已經(jīng)說了pthread_detach能夠線程屬性改變?yōu)榉蛛x狀態(tài),在退出時自動釋放其分配的資源。 不需要其他線程需要連接它竹椒。 但是默認(rèn)情況下所有線程都是可連接(Joinable)童太,因此要分離一個線程,我們需要使用指定的線程ID傳遞給pthread_detach系統(tǒng)調(diào)用胸完,并且在主線程中調(diào)用它,我們上面的main線程书释,將pthread_joint替換成pthread_detach即可,如下代碼

int main(int argc,char* argv){

    pthread_t tids[3];

    Sequen seq[3]={
        {1,50,0},
        {51,100,0},
        {101,200,0}
    };

    int err;
    for(int i=0;i<3;i++){
        printf("創(chuàng)建地第%d個子線程\n",i+1);
        err=pthread_create(&tids[i],NULL,calc,&seq[i]);
        if (err){
            printf("創(chuàng)建線程失敗!!\n");
            continue;
        }
        pthread_detach(tids[i]);
    }

    printf("主線程計算結(jié)果:%ld\n",seq[0].sum+seq[1].sum+seq[2].sum);
    return 0;
}

我們編譯后嘗試運(yùn)行一下赊窥,從下面的輸出爆惧,三個線程ID是不一樣的證明子線程可以并發(fā)運(yùn)行了,但是主線程是提前return了锨能,因為整個程序計算的整數(shù)序列的輸出結(jié)果是不對的扯再。

ss18.png

那么究竟有沒有方法可以保證所以子線程可以并發(fā)運(yùn)行,同時主線程在確認(rèn)所有子線程返回計算結(jié)果后腹侣,主線程再執(zhí)行剩余的操作再返回叔收?你可能會想到pthread_exit調(diào)用嗎!非常抱歉pthread_exit在這里不適用傲隶,因為pthread_exit之后的所有語句都是不會再執(zhí)行的。

我們先將這個問題窃页,留給下一篇文章來解決.后續(xù)繼續(xù)更新....

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末跺株,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子脖卖,更是在濱河造成了極大的恐慌乒省,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件畦木,死亡現(xiàn)場離奇詭異袖扛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門蛆封,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唇礁,“玉大人,你說我怎么就攤上這事惨篱≌悼穑” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵砸讳,是天一觀的道長琢融。 經(jīng)常有香客問我,道長簿寂,這世上最難降的妖魔是什么漾抬? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮常遂,結(jié)果婚禮上纳令,老公的妹妹穿的比我還像新娘。我一直安慰自己烈钞,他們只是感情好泊碑,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著毯欣,像睡著了一般馒过。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酗钞,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天腹忽,我揣著相機(jī)與錄音,去河邊找鬼砚作。 笑死窘奏,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的葫录。 我是一名探鬼主播着裹,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼米同!你這毒婦竟也來了骇扇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤面粮,失蹤者是張志新(化名)和其女友劉穎少孝,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體熬苍,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稍走,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婿脸。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡粱胜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盖淡,到底是詐尸還是另有隱情年柠,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布褪迟,位于F島的核電站冗恨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏味赃。R本人自食惡果不足惜掀抹,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望心俗。 院中可真熱鬧傲武,春花似錦、人聲如沸城榛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狠持。三九已至疟位,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間喘垂,已是汗流浹背甜刻。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留正勒,地道東北人得院。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像章贞,于是被迫代替她去往敵國和親祥绞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351