linux系統編程--線程


安裝線程man page,命令:sudo apt-get install manpages-posix-dev

線程概念

什么是線程

LWP:light weight process 輕量級的進程,本質仍是進程(在Linux環境下)

         進程:獨立地址空間,擁有PCB

         線程:也有PCB,但沒有獨立的地址空間(共享)

         區別:在於是否共享地址空間。         獨居(進程);合租(線程)。

         Linux下:           線程:最小的執行單位

                                    進程:最小分配資源單位,可看成是只有一個線程的進程。

在程序中創建線程,可以提高效率,進程內線程越多,爭奪到CPU的概率就越大,執行代碼的概率就越大(有一個度)。CPU將線程和進程等同處理

Linux內核線程實現原理

類Unix系統中,早期是沒有“線程”概念的,80年代才引入,借助進程機制實現出了線程的概念。因此在這類系統中,進程和線程關系密切。

1. 輕量級進程(light-weight process),也有PCB,創建線程使用的底層函數和進程一樣,都是clone

2. 從內核里看進程和線程是一樣的,都有各自不同的PCB,但是PCB中指向內存資源的三級頁表是相同的

3. 進程可以蛻變成線程

4. 線程可看做寄存器和棧的集合

5. 在linux下,線程最是小的執行單位;進程是最小的分配資源單位

察看LWP號:ps –Lf pid 查看指定線程的lwp號。ps -eLf

UID         PID   PPID    LWP  C NLWP STIME TTY      STAT   TIME CMD
gec        6699   1784   6699 12   54 06:12 ?        Sl     0:03 /usr/lib/firefox/firefox
gec        6699   1784   6706  0   54 06:12 ?        Sl     0:00 /usr/lib/firefox/firefox
gec        6699   1784   6707  0   54 06:12 ?        Sl     0:00 /usr/lib/firefox/firefox

 

 

三級映射:進程PCB --> 頁目錄(可看成數組,首地址位於PCB中) --> 頁表 --> 物理頁面 --> 內存單元

參考:《Linux內核源代碼情景分析》 ----毛德操

對於進程來說,相同的地址(同一個虛擬地址)在不同的進程中,反復使用而不沖突。原因是他們雖虛擬址一樣,但,頁目錄、頁表、物理頁面各不相同

相同的虛擬址,映射到不同的物理頁面內存單元,最終訪問不同的物理頁面。

但!線程不同!兩個線程具有各自獨立的PCB,但共享同一個頁目錄,也就共享同一個頁表和物理頁面。所以兩個PCB共享一個地址空間

         實際上,無論是創建進程的fork,還是創建線程的pthread_create,底層實現都是調用同一個內核函數clone。

         如果復制對方的地址空間,那么就產出一個“進程”;如果共享對方的地址空間,就產生一個“線程”。

         因此:Linux內核是不區分進程和線程的。只在用戶層面上進行區分。所以,線程所有操作函數 pthread_* 是庫函數,而非系統調用

 

線程共享資源

http://www.cnblogs.com/youxin/p/4102883.html

         1.文件描述符表

         2.每種信號的處理方式

 

盡量不要在一個程序中,即使用線程又使用信號,這會比較復雜,不好調試。

線程共享信號處理方式,所以當一個信號來的時候,到底哪一個線程去處理(誰先搶到誰處理)。或者可以單獨設置mask阻塞信號(信號屏蔽字)

         3.當前工作目錄

         4.用戶ID和組ID

         5.內存地址空間 (.text/.data/.bss/heap/共享庫) 。每個線程是唯一的執行單位,因此棧不能共享

線程非共享資源

         1.線程id

         2.處理器現場和棧指針(內核棧)

         3.獨立的棧空間(用戶空間棧)

         4.errno變量

         5.信號屏蔽字

         6.調度優先級

線程優、缺點

         優點:     1. 提高程序並發性        2. 開銷小        3. 數據通信、共享數據方便

         缺點:     1. 庫函數,不穩定        2. 調試、編寫困難、gdb不支持          3. 對信號支持不好

         優點相對突出,缺點均不是硬傷。Linux下由於實現方法導致進程、線程差別不是很大。

線程控制原語

pthread_self函數

獲取線程ID。其作用對應進程中 getpid() 函數。

         pthread_t pthread_self(void); 返回值:成功:0;       失敗:無!

         線程ID:pthread_t類型,本質:在Linux下為無符號整數(%lu),其他系統中可能是結構體實現

         線程ID是進程內部,識別標志。(兩個進程間,線程ID允許相同)

         注意:不應使用全局變量 pthread_t tid,在子線程中通過pthread_create傳出參數來獲取線程ID,而應使用pthread_self。

pthread_create函數

創建一個新線程。其作用,對應進程中fork() 函數。

         int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

         返回值:成功:0;       失敗:錯誤號         -----Linux環境下,所有線程特點,失敗均直接返回錯誤號

參數:    

         pthread_t:當前Linux中可理解為:typedef unsigned long int pthread_t。

    1. 傳出參數,保存系統為我們分配好的線程ID

         2. 通常傳NULL,表示使用線程默認屬性。若想使用具體屬性也可以修改該參數。

         3. 函數指針,指向線程主函數(線程體),該函數運行結束,則線程結束

         4. 線程主函數執行期間所使用的參數(給回調函數傳參)

在一個線程中調用pthread_create()創建新的線程后,當前線程從pthread_create()返回繼續往下執行,而新的線程所執行的代碼由我們傳給pthread_create的函數指針start_routine決定。start_routine函數接收一個參數,是通過pthread_create的arg參數傳遞給它的,該參數的類型為void *,這個指針按什么類型解釋由調用者自己定義。start_routine的返回值類型也是void *,這個指針的含義同樣由調用者自己定義。start_routine返回時,這個線程就退出了,其它線程可以調用pthread_join得到start_routine的返回值,類似於父進程調用wait(2)得到子進程的退出狀態,稍后詳細介紹pthread_join。

pthread_create成功返回后,新創建的線程的id被填寫到thread參數所指向的內存單元。我們知道進程id的類型是pid_t,每個進程的id在整個系統中是唯一的,調用getpid(2)可以獲得當前進程的id,是一個正整數值。線程id的類型是thread_t,它只在當前進程中保證是唯一的,在不同的系統中thread_t這個類型有不同的實現,它可能是一個整數值,也可能是一個結構體,也可能是一個地址,所以不能簡單地當成整數用printf打印,調用pthread_self(3)可以獲得當前線程的id。

attr參數表示線程屬性,本節不深入討論線程屬性,所有代碼例子都傳NULL給attr參數,表示線程屬性取缺省值,感興趣的讀者可以參考APUE。

【練習】:創建一個新線程,打印線程ID。注意:鏈接線程庫 -lpthread  【pthrd_crt.c】

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

void *tfn(void *arg)
{
    printf("tfn-- pid = %d, tid = %lu\n", getpid(), pthread_self());
    return (void *)0;
}

int main(void)
{
    pthread_t tid;
    printf("main -- pid = %d, tid = %lu\n", getpid(), pthread_self());

    int ret = pthread_create(&tid, NULL, tfn, NULL);
    if (ret != 0) {
        //printf("pthread_create erro: %s\n", strerror(ret));
        fprintf(stderr, "pthread_create erro: %s\n", strerror(ret));
        exit(1);
    }
    sleep(1);

    return 0;
}
pid = 125776,pthread = 2094708480
pid = 125776,pthread = 2086381312

 

由於pthread_create的錯誤碼不保存在errno中,因此不能直接用perror(3)打印錯誤信息,可以先用strerror(3)把錯誤碼轉換成錯誤信息再打印。

如果任意一個線程調用了exit或_exit,則整個進程的所有線程都終止,由於從main函數return也相當於調用exit,為了防止新創建的線程還沒有得到執行就終止,

我們在main函數return之前延時1秒,這只是一種權宜之計,即使主線程等待1秒,內核也不一定會調度新創建的線程執行,下一節我們會看到更好的辦法。

【練習】:循環創建多個線程,每個線程打印自己是第幾個被創建的線程。(類似於進程循環創建子進程)   【more_pthrd.c】

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

void *tfn(void *arg)
{
    int i;

    i = (int)arg;
    sleep(i);     //通過i來區別每個線程
    printf("I'm %dth thread, Thread_ID = %lu\n", i+1, pthread_self());

    return NULL;
}

int main(int argc, char *argv[])
{
    int n = 5, i;
    pthread_t tid;

    if (argc == 2)
        n = atoi(argv[1]);

    for (i = 0; i < n; i++) {
        pthread_create(&tid, NULL, tfn, (void *)i);
        //將i轉換為指針,在tfn中再強轉回整形。
    }
    sleep(n);
    printf("I am main, and I am not a process, I'm a thread!\n" 
            "main_thread_ID = %lu\n", pthread_self());

    pthread_exit(NULL);
}
I'm 1th thread, Thread_ID = 140579885291264
I'm 2th thread, Thread_ID = 140579876898560
I'm 3th thread, Thread_ID = 140579868505856
I'm 4th thread, Thread_ID = 140579860113152
I'm 5th thread, Thread_ID = 140579851720448
I am main, and I am not a process, I'm a thread!
main_thread_ID = 140579893618432

 

拓展思考:將pthread_create函數參4修改為(void *)&i, 將線程主函數內改為 i=*((int *)arg) 是否可以。  i++會出現錯亂           

線程與共享

牢記】:線程默認共享數據段、代碼段等地址空間,常用的是全局變量而進程不共享全局變量,只能借助mmap。

【練習】:設計程序,驗證線程之間共享全局數據。【glb_var_pthrd.c】

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

int var = 100;

void *tfn(void *arg)
{
    var = 200;
    printf("thread\n");

    return NULL;
}

int main(void)
{
    printf("At first var = %d\n", var);

    pthread_t tid;
    pthread_create(&tid, NULL, tfn, NULL);
    sleep(1);

    printf("after pthread_create, var = %d\n", var);

    return 0;
}

 

At first var = 100
thread
after pthread_create, var = 200

 

pthread_exit函數

將單個線程退出

         void pthread_exit(void *retval);      參數:retval表示線程退出狀態,通常傳NULL

思考:使用exit將指定線程退出,可以嗎?【pthrd_exit.c】

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

void *tfn(void *arg)
{
    int i;

    i = (int)arg;//強轉。
    if (i == 2)
        pthread_exit(NULL);

    sleep(i);     //通過i來區別每個線程
    printf("I'm %dth thread, Thread_ID = %lu\n", i+1, pthread_self());

    return NULL;
}

int main(int argc, char *argv[])
{
    int n = 5, i;
    pthread_t tid;

    if (argc == 2)
        n = atoi(argv[1]);

    for (i = 0; i < n; i++) {
        pthread_create(&tid, NULL, tfn, (void *)i);
        //將i轉換為指針,在tfn中再強轉回整形。
    }
    sleep(n);
    printf("I am main, I'm a thread!\n" 
            "main_thread_ID = %lu\n", pthread_self());

    return 0;
}

 

         結論:線程中,禁止使用exit函數,會導致進程內所有線程全部退出。

         在不添加sleep控制輸出順序的情況下。pthread_create在循環中,幾乎瞬間創建5個線程,但只有第1個線程有機會輸出(或者第2個也有,也可能沒有,取決於內核調度)如果第3個線程執行了exit,將整個進程退出了,所以全部線程退出了

         所以,多線程環境中,應盡量少用,或者不使用exit函數,取而代之使用pthread_exit函數,將單個線程退出。任何線程里exit導致進程退出,其他線程未工作結束,主控線程退出時不能return或exit。

  另注意,pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是用malloc分配的不能在線程函數的棧上分配因為當其它線程得到這個返回指針時線程函數已經退出了

【練習】:編寫多線程程序,總結exit、return、pthread_exit各自退出效果。

         return:返回到調用者那里去。

         pthread_exit():將調用該函數的線程退出                   

         exit: 將進程退出。

pthread_join函數

阻塞等待線程退出,獲取線程退出狀態              其作用,對應進程中 waitpid() 函數

 int pthread_join(pthread_t thread, void **retval); 成功:0;失敗:錯誤號

參數:thread:線程ID (【注意】:不是指針);retval:存儲線程結束狀態。

對比記憶:

                   進程中:main返回值、exit參數-->int;等待子進程結束 wait 函數參數-->int *

                   線程中:線程主函數返回值、pthread_exit-->void *;等待線程結束 pthread_join 函數參數-->void **

【練習】:參數 retval 非空用法。【pthrd_exit_join.c】

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

typedef struct{
    int a;
    int b;
} exit_t;

void *tfn(void *arg)
{
    exit_t *ret;
    ret = malloc(sizeof(exit_t)); 

    ret->a = 100;
    ret->b = 300;
    pthread_exit((void *)ret);

    return NULL; //should not be here.
}

int main(void)
{
    pthread_t tid;
    exit_t *retval;

    pthread_create(&tid, NULL, tfn, NULL);
    /*調用pthread_join可以獲取線程的退出狀態*/
    pthread_join(tid, (void **)&retval);
    printf("a = %d, b = %d \n", retval->a, retval->b);

    return 0;
}

 

調用該函數的線程將掛起等待,直到id為thread的線程終止。thread線程以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下:

1.       如果thread線程通過return返回,retval所指向的單元里存放的是thread線程函數的返回值

2.       如果thread線程被別的線程調用pthread_cancel異常終止掉,retval所指向的單元里存放的是常數PTHREAD_CANCELED。

3.       如果thread線程是自己調用pthread_exit終止的,retval所指向的單元存放的是傳給pthread_exit的參數

4.       如果對thread線程的終止狀態不感興趣,可以傳NULL給retval參數。

 

【練習】:使用pthread_join函數將循環創建的多個子線程回收。【pthrd_loop_join.c】

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

int var = 100;

void *tfn(void *arg)
{
    int i;
    i = (int)arg;
    
    sleep(i);
    if (i == 1) {    //i = 0  100  333  333  777  777  
        var = 333;
        printf("var = %d\n", var);
        pthread_exit((void *)var);

    } else  if (i == 3) {
        var = 777;
        printf("I'm %dth pthread, pthread_id = %lu\n var = %d\n", i+1, pthread_self(), var);
        pthread_exit((void *)var);

    } else  {

        printf("I'm %dth pthread, pthread_id = %lu\n var = %d\n", i+1, pthread_self(), var);
        pthread_exit((void *)var);
    }

    return NULL;
}

int main(void)
{
    pthread_t tid[5];
    int i, *ret[5];

    for (i = 0; i < 5; i++)
        pthread_create(&tid[i], NULL, tfn, (void *)i);

    for (i = 0; i < 5; i++) {
        pthread_join(tid[i], (void **)&ret[i]);
        printf("-------%d 's ret = %d\n", i, (int)ret[i]);
    }
    printf("I'm main pthread tid = %lu\t var = %d\n", pthread_self(), var);

    pthread_exit(NULL);
}
View Code

 

pthread_detach函數

實現線程分離

         int pthread_detach(pthread_t thread);  成功:0;失敗:錯誤號

         線程分離狀態:指定該狀態,線程主動與主控線程斷開關系線程結束后,其退出狀態不由其他線程獲取,而直接自己自動釋放。網絡、多線程服務器常用。

         進程若有該機制,將不會產生僵屍進程。僵屍進程的產生主要由於進程死后,大部分資源被釋放,一點殘留資源仍存於系統中,導致內核認為該進程仍存在。

         也可使用 pthread_create函數參2(線程屬性)來設置線程分離。

【練習】:使用pthread_detach函數實現線程分離 【pthrd_detach.c】

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

void *tfn(void *arg)
{
    int n = 3;
    while (n--) {
        printf("thread count %d\n", n);
        sleep(1);
    }
    return (void *)1;
}

int main(void)
{
    pthread_t tid;
    void *tret;
    int err;

#if 1

    pthread_attr_t attr;            /*通過線程屬性來設置游離態*/

    pthread_attr_init(&attr);

    pthread_attr_setdetachstate(&attr,    PTHREAD_CREATE_DETACHED);

    pthread_create(&tid, &attr, tfn, NULL);

    pthread_attr_destroy(&attr);

#else

    pthread_create(&tid, NULL, tfn, NULL);
    pthread_detach(tid);        

#endif

    while (1) {
        err = pthread_join(tid, &tret);
        if (err != 0)
            fprintf(stderr, "thread %s\n", strerror(err));
        else
            fprintf(stderr, "thread exit code %d\n", (int)tret);
        sleep(1);
    }

    return 0;
}
View Code

一般情況下,線程終止后,其終止狀態一直保留到其它線程調用pthread_join獲取它的狀態為止

但是線程也可以被置為detach狀態,這樣的線程一旦終止就立刻回收它占用的所有資源,而不保留終止狀態。

不能對一個已經處於detach狀態的線程調用pthread_join,這樣的調用將返回EINVAL。如果已經對一個線程調用了pthread_detach就不能再調用pthread_join了。

pthread_cancel函數

殺死(取消)線程                         其作用,對應進程中 kill() 函數。

         int pthread_cancel(pthread_t thread);   成功:0;失敗:錯誤號

         【注意】:線程的取消並不是實時的,而又一定的延時。需要等待線程到達某個取消點(檢查點)。

         類似於玩游戲存檔,必須到達指定的場所(存檔點,如:客棧、倉庫、城里等)才能存儲進度。殺死線程也不是立刻就能完成,必須要到達取消點。

         取消點:是線程檢查是否被取消,並按請求進行動作的一個位置。通常是一些系統調用creat,open,pause,close,read,write.....    ——參 APUE.12.7 取消選項。

   可粗略認為一個系統調用(進入內核)即為一個取消點

被取消的線程,退出值,定義在Linux的pthread庫中常數PTHREAD_CANCELED的值是-1。

可以在頭文件pthread.h中找到它的定義:#define PTHREAD_CANCELED ((void *) -1)

【練習】:終止線程的三種方法。注意“取消點”的概念。【pthrd_endof3.c】 

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

void *tfn1(void *arg)
{
    printf("thread 1 returning\n");
    return (void *)111; 
}

void *tfn2(void *arg)
{
    printf("thread 2 exiting\n");
    pthread_exit((void *)222);
}

void *tfn3(void *arg)
{
    while (1) {
//        printf("thread 3: I'm going to die 1 seconds after...\n");
//        sleep(1);
        pthread_testcancel();    //自己添加取消點
    }
}

int main(void)
{
    pthread_t tid;
    void *tret = NULL;

    pthread_create(&tid, NULL, tfn1, NULL);
    pthread_join(tid, &tret);
    printf("thread 1 exit code = %d\n\n", (int)tret);

    pthread_create(&tid, NULL, tfn2, NULL);
    pthread_join(tid, &tret);
    printf("thread 2 exit code = %d\n\n", (int)tret);

    pthread_create(&tid, NULL, tfn3, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &tret);
    printf("thread 3 exit code = %d\n", (int)tret);

    return 0;
}

 

終止線程方式

總結:終止某個線程而不終止整個進程,有三種方法:

1.       從線程主函數return。這種方法對主控線程不適用,從main函數return相當於調用exit。

2.       一個線程可以調用pthread_cancel終止同一進程中的另一個線程。

3.       線程可以調用pthread_exit終止自己。

pthread_equal函數

比較兩個線程ID是否相等。

         int pthread_equal(pthread_t t1, pthread_t t2);

         有可能Linux在未來線程ID pthread_t 類型被修改為結構體實現。

控制原語對比

     進程                            線程

     fork                     pthread_create

     exit                     pthread_exit

     wait                    pthread_join

     kill                       pthread_cancel

     getpid                pthread_self              命名空間

線程屬性

本節作為指引性介紹,linux下線程的屬性是可以根據實際項目需要,進行設置,之前我們討論的線程都是采用線程的默認屬性,默認屬性已經可以解決絕大多數開發時遇到的問題

如我們對程序的性能提出更高的要求那么需要設置線程屬性,比如可以通過設置線程棧的大小來降低內存的使用,增加最大線程個數

typedef struct

{
int                                        etachstate;      //線程的分離狀態
int                                        schedpolicy;   //線程調度策略
struct sched_param         schedparam; //線程的調度參數
int                                        inheritsched; //線程的繼承性
int                                        scope;              //線程的作用域
size_t                                   guardsize;        //線程棧末尾的警戒緩沖區大小
int                                          stackaddr_set; //線程的棧設置
void*                                   stackaddr;       //線程棧的位置
size_t                                   stacksize;         //線程棧的大小
} pthread_attr_t;

 

主要結構體成員:

         1. 線程分離狀態

         2. 線程棧大小(默認平均分配)

         3. 線程棧警戒緩沖區大小(位於棧末尾) 參 APUE.12.3 線程屬性

         4. 線程棧最低地址

屬性值不能直接設置,須使用相關函數進行操作,初始化的函數為pthread_attr_init這個函數必須在pthread_create函數之前調用。之后須用pthread_attr_destroy函數來釋放資源。

線程屬性主要包括如下屬性:作用域(scope)、棧尺寸(stack size)、棧地址(stack address)、優先級(priority)、分離的狀態(detached state)、調度策略和參數(scheduling policy and parameters)。

默認的屬性為非綁定、非分離、缺省的堆棧、與父進程同樣級別的優先級。

線程屬性初始化

注意:應先初始化線程屬性,再pthread_create創建線程

初始化線程屬性

int pthread_attr_init(pthread_attr_t *attr); 成功:0;失敗:錯誤號

銷毀線程屬性所占用的資源

int pthread_attr_destroy(pthread_attr_t *attr); 成功:0;失敗:錯誤號

線程的分離狀態

線程的分離狀態決定一個線程以什么樣的方式來終止自己。

非分離狀態:線程的默認屬性是非分離狀態,這種情況下,原有的線程等待創建的線程結束。只有當pthread_join()函數返回時,創建的線程才算終止,才能釋放自己占用的系統資源。

分離狀態:分離線程沒有被其他的線程所等待,自己運行結束了,線程也就終止了,馬上釋放系統資源。應該根據自己的需要,選擇適當的分離狀態。

線程分離狀態的函數:

設置線程屬性,分離or非分離

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

獲取程屬性,分離or非分離

        pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);

         參數:     attr:已初始化的線程屬性

        detachstate: PTHREAD_CREATE_DETACHED(分離線程)

                     PTHREAD _CREATE_JOINABLE(非分離線程)

這里要注意的一點是,如果設置一個線程為分離線程,而這個線程運行又非常快,它很可能在pthread_create函數返回之前就終止了,它終止以后就可能將線程號和系統資源移交給其他的線程使用,

這樣調用pthread_create的線程就得到了錯誤的線程號。要避免這種情況可以采取一定的同步措施,最簡單的方法之一是可以在被創建的線程里調用pthread_cond_timedwait函數,

讓這個線程等待一會兒,留出足夠的時間讓函數pthread_create返回。設置一段等待時間,是在多線程編程里常用的方法。但是注意不要使用諸如wait()之類的函數,它們是使整個進程睡眠,並不能解決線程同步的問題。

線程的棧地址

POSIX.1定義了兩個常量_POSIX_THREAD_ATTR_STACKADDR_POSIX_THREAD_ATTR_STACKSIZE檢測系統是否支持棧屬性。

也可以給sysconf函數傳遞_SC_THREAD_ATTR_STACKADDR_SC_THREAD_ATTR_STACKSIZE來進行檢測。

當進程棧地址空間不夠用時指定新建線程使用由malloc分配的空間作為自己的棧空間。通過pthread_attr_setstack和pthread_attr_getstack兩個函數分別設置和獲取線程的棧地址。

int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize); 成功:0;失敗:錯誤號

int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize); 成功:0;失敗:錯誤號

參數:     attr:指向一個線程屬性的指針

stackaddr:返回獲取的棧地址

stacksize:返回獲取的棧大小

線程的棧大小

當系統中有很多線程時,可能需要減小每個線程棧的默認大小,防止進程的地址空間不夠用,當線程調用的函數會分配很大的局部變量或者函數調用層次很深時,可能需要增大線程棧的默認大小。

函數pthread_attr_getstacksize和 pthread_attr_setstacksize提供設置。

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize); 成功:0;失敗:錯誤號

int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize); 成功:0;失敗:錯誤號

參數:     attr:指向一個線程屬性的指針

stacksize:返回線程的堆棧大小

線程屬性控制示例                                                                                       

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

#define SIZE 0x10

void *th_fun(void *arg)
{
    while (1) 
        sleep(1);
}

int main(void)
{
    pthread_t tid;
    int err, detachstate, i = 1;
    pthread_attr_t attr;
    size_t stacksize;
    void *stackaddr;

    pthread_attr_init(&attr);        
    pthread_attr_getstack(&attr, &stackaddr, &stacksize);
    pthread_attr_getdetachstate(&attr, &detachstate);

    if (detachstate == PTHREAD_CREATE_DETACHED)
        printf("thread detached\n");
    else if (detachstate == PTHREAD_CREATE_JOINABLE)
        printf("thread join\n");
    else
        printf("thread un known\n");

    /* 設置線程分離屬性 */
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    while (1) {
        /* 在堆上申請內存,指定線程棧的起始地址和大小 */
        stackaddr = malloc(SIZE);
        if (stackaddr == NULL) {
            perror("malloc");
            exit(1);
        }
        stacksize = SIZE;
        pthread_attr_setstack(&attr, stackaddr, stacksize);

        err = pthread_create(&tid, &attr, th_fun, NULL);
        if (err != 0) {
            printf("%s\n", strerror(err));
            exit(1);
        }
        printf("%d\n", i++);
    }

    pthread_attr_destroy(&attr);

    return 0;
}

 

NPTL

1.察看當前pthread庫版本getconf GNU_LIBPTHREAD_VERSION

2.NPTL實現機制(POSIX),Native POSIX Thread Library

3.使用線程庫時gcc指定 –lpthread

線程使用注意事項

1.       主線程退出其他線程不退出,主線程應調用pthread_exit

2.       避免僵屍線程

pthread_join

pthread_detach

pthread_create指定分離屬性

被join線程可能在join函數返回前就釋放完自己的所有內存資源,所以不應當返回被回收線程棧中的值;

3.       malloc和mmap申請的內存可以被其他線程釋放

4.       應避免在多線程模型中調用fork除非,馬上exec,子進程中只有調用fork的線程存在,其他線程在子進程中均pthread_exit

5.       信號的復雜語義很難和多線程共存,應避免在多線程引入信號機制


免責聲明!

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



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