Linux C編程之十四 線程、線程控制、線程屬性


一、整體大綱

二、線程相關

1. 什么是線程     

                                                    

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

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

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

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

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

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

2. Linux內核線程實現原理

    (1)線程實現原理

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

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

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

    3)進程可以蛻變成線程

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

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

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

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

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

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

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

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

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

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

    (2)線程共享資源

     1)文件描述符表

     2)每種信號的處理方式

     3)當前工作目錄

     4)用戶ID和組ID

     5)內存地址空間 (.text/.data/.bss/heap/共享庫)

    (3)線程非共享資源

     1)線程id

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

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

     4)errno變量

     5)信號屏蔽字

     6)調度優先級

   (4)線程優、缺點

    優點:

          1)提高程序並發性

          2)開銷小

          3)數據通信、共享數據方便

    缺點:

          1)庫函數,不穩定

          2)調試、編寫困難、gdb不支持

          3)對信號支持不好

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

3. 線程控制相關函數

    (1)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。

    (2)pthread_create函數

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

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

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

    參數:

           thread 線程的id,傳出參數,pthread_t:當前Linux中可理解為:typedef  unsigned long int  pthread_t;

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

           第三個參數函數指針 void *func(void *),指向線程主函數(線程體),該函數運行結束,則線程結束。

           arg 線程主函數執行期間所使用的參數。

    返回值:

          成功返回0

          失敗返回errno

    注意:編譯的時候需要加pthread庫(Compile and link with -pthread.)

    在一個線程中調用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 

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 
 5 void *thr(void *arg)
 6 {
 7     printf("I am a thread! pid = %d, tid = %lu\n", getpid(), pthread_self());
 8     return NULL;
 9 }
10 
11 int main()
12 {
13     pthread_t tid;
14     pthread_create(&tid, NULL, thr, NULL);
15     printf("I am main thread, pid = %d, tid = %lu\n", getpid(), pthread_self());
16     sleep(1);
17 
18     return 0;
19 }
pthread_create示例

    由於pthread_create的錯誤碼不保存在errno中,因此不能直接用perror(3)打印錯誤信息,可以先用strerror(3)把錯誤碼轉換成錯誤信息再打印。如果任意一個線程調用了exit或_exit,則整個進程的所有線程都終止,由於從main函數return也相當於調用exit,為了防止新創建的線程還沒有得到執行就終止,我們在main函數return之前延時1秒,這只是一種權宜之計,即使主線程等待1秒,內核也不一定會調度新創建的線程執行,下一節我們會看到更好的辦法。

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

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 #include <stdlib.h>
 5 #include <string.h>
 6 
 7 void *thr(void *arg)
 8 {
 9     int num = (int)arg;
10     printf("I am %d thread, self = %lu\n", num, pthread_self());
11 
12     return (void *)(100 + num);
13 }
14 
15 int main()
16 {
17     pthread_t tid[5];
18     int i = 0;
19     for (i = 0; i < 5; i++)
20     {
21         pthread_create(&tid[i], NULL, thr, (void*)i);
22     }
23 
24     for (i = 0; i < 5; i++)
25     {
26         void *ret;
27         pthread_join(tid[i], &ret);
28         printf("i = %d, ret = %d\n", i, (int)ret);
29     }
30 
31     return 0;
32 }
n_pthread_create.c

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

    線程與共享

    線程間共享全局變量!

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

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

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 #include <stdlib.h>
 5 #include <string.h>
 6 
 7 int var = 100;
 8 
 9 void *thr(void *arg)
10 {
11     printf("I am a thread, self = %lui, var = %d\n", pthread_self(), var);
12     sleep(2);
13     var = 101;
14     printf("I am a thread, self = %lu, var = %d\n", pthread_self(), var);
15 
16     return NULL;
17 }
18 
19 int main()
20 {
21     pthread_t tid;
22     pthread_create(&tid, NULL, thr, NULL);
23     printf("I am main thread, pid = %d, tid = %lu\n", getpid(), pthread_self());
24 
25     pthread_detach(tid); //線程分離
26     printf("I am main thread, self = %lui, var = %d\n", pthread_self(), var);
27     var = 1003;
28     sleep(5);
29     printf("I am main thread, self = %lui, var = %d\n", pthread_self(), var);
30 
31     return 0;
32 }
線程共享全局變量

    (3)pthread_exit函數

    將單個線程退出

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

    思考:使用exit將指定線程退出,可以嗎? 

    結論:線程中,禁止使用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: 將進程退出
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 #include <stdlib.h>
 5 
 6 void *thr(void *arg)
 7 {
 8     printf("I am a thread! pid = %d, tid = %lu\n", getpid(), pthread_self());
 9     //return NULL;
10     pthread_exit(NULL);
11     //exit(1);
12 }
13 
14 int main()
15 {
16     pthread_t tid;
17     pthread_create(&tid, NULL, thr, NULL);
18     printf("I am main thread, pid = %d, tid = %lu\n", getpid(), pthread_self());
19 
20     sleep(10);
21     printf("I will out.\n");
22 
23     pthread_exit(NULL);
24 
25     return 0;
26 }
線程退出

    (4)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 非空用法。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 #include <stdlib.h>
 5 
 6 void *thr(void *arg)
 7 {
 8     printf("I am a thread! pid = %d, tid = %lu\n", getpid(), pthread_self());
 9     //return (void*)100; //這樣退出線程也可以
10     pthread_exit((void *)100);
11 }
12 
13 int main()
14 {
15     pthread_t tid;
16     pthread_create(&tid, NULL, thr, NULL);
17     printf("I am main thread, pid = %d, tid = %lu\n", getpid(), pthread_self());
18 
19     void *ret = NULL;
20     pthread_join(tid, &ret); //阻塞等待
21 
22     printf("ret exit with %d\n", (int)ret);
23 
24     pthread_exit(NULL);
25 
26     return 0;
27 }
pthreat_join接收返回值

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

  • 如果thread線程通過return返回,retval所指向的單元里存放的是thread線程函數的返回值。
  • 如果thread線程被別的線程調用pthread_cancel異常終止掉,retval所指向的單元里存放的是常數PTHREAD_CANCELED。
  • 如果thread線程是自己調用pthread_exit終止的,retval所指向的單元存放的是傳給pthread_exit的參數。
  • 如果對thread線程的終止狀態不感興趣,可以傳NULL給retval參數。

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

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 #include <stdlib.h>
 5 #include <string.h>
 6 
 7 void *thr(void *arg)
 8 {
 9     int num = (int)arg;
10     printf("I am %d thread, self = %lu\n", num, pthread_self());
11 
12     return (void *)(100 + num);
13 }
14 
15 int main()
16 {
17     pthread_t tid[5];
18     int i = 0;
19     for (i = 0; i < 5; i++)
20     {
21         pthread_create(&tid[i], NULL, thr, (void*)i);
22     }
23 
24     for (i = 0; i < 5; i++)
25     {
26         void *ret;
27         pthread_join(tid[i], &ret);
28         printf("i = %d, ret = %d\n", i, (int)ret);
29     }
30 
31     return 0;
32 }
創建多個子線程並回收

    (5)pthread_detach函數

    實現線程分離

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

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

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

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

    練習:使用pthread_detach函數實現線程分離 。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 #include <stdlib.h>
 5 #include <string.h>
 6 
 7 void *thr(void *arg)
 8 {
 9     printf("I am a thread, self = %lu\n", pthread_self());
10     sleep(4);
11     printf("I am a thread, self = %lu\n", pthread_self());
12 
13     return NULL;
14 }
15 
16 int main()
17 {
18     pthread_t tid;
19     pthread_create(&tid, NULL, thr, NULL);
20     printf("I am main thread, pid = %d, tid = %lu\n", getpid(), pthread_self());
21 
22     pthread_detach(tid); //線程分離
23 
24     sleep(5);
25 
26     //設置線程分離,如果再使用pthread_join回收線程會報錯
27     /*
28     int ret = 0;
29     if ((ret = pthread_join(tid, NULL)) > 0) //阻塞等待
30     {
31         printf("join err: %d, %s\n", ret, strerror(ret));
32     }*/
33 
34     return 0;
35 }
pthread_detach線程分離

    一般情況下,線程終止后,其終止狀態一直保留到其它線程調用pthread_join獲取它的狀態為止。但是線程也可以被置為detach狀態,這樣的線程一旦終止就立刻回收它占用的所有資源,而不保留

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

    (6)pthread_cancel函數

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

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

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

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

取消點:是線程檢查是否被取消,並按請求進行動作的一個位置。通常是一些系統調用creat,open,pause,close,read,write..... 執行命令man 7 pthreads可以查看具備這些取消點的系統調用列

表。也可參閱 APUE.12.7 取消選項小節。

    可粗略認為一個系統調用(進入內核)即為一個取消點。如線程中沒有取消點,可以通過調用pthreestcancel函數自行設置一個取消點。

    被取消的線程, 退出值定義在Linux的pthread庫中。常數PTHREAD_CANCELED的值是-1。可在頭文件pthread.h中找到它的定義:#define PTHREAD_CANCELED ((void *) -1)。因此當我們對一

個已經被取消的線程使用pthread_join回收時,得到的返回值為-1。

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

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 #include <stdlib.h>
 5 
 6 void *thr(void *arg)
 7 {
 8     while(1)
 9     {
10         //注意:如果沒有下面這兩行代碼,也就是只是一個while(1)死循環,則無法殺死該線程
11         //或者加上pthread_testcancel();設置殺死點,函數也可以被主線程殺死
12         printf("I am a thread! pid = %d, tid = %lu\n", getpid(), pthread_self());
13         sleep(1);
14         //pthread_testcancel();
15     }
16 
17     return NULL;
18 }
19 
20 int main()
21 {
22     pthread_t tid;
23     pthread_create(&tid, NULL, thr, NULL);
24     printf("I am main thread, pid = %d, tid = %lu\n", getpid(), pthread_self());
25 
26     sleep(5);
27     pthread_cancel(tid); //殺死線程
28 
29     void *ret;
30     pthread_join(tid, &ret); //阻塞等待
31     printf("thread exit with %d\n", (int)ret);
32 
33     return 0;
34 }
pthread_cancel線程取消

    終止線程方式:

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

  • 從線程主函數return。這種方法對主控線程不適用,從main函數return相當於調用exit。
  • 一個線程可以調用pthread_cancel終止同一進程中的另一個線程。
  • 線程可以調用pthread_exit終止自己。

    (7)pthread_equal函數

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

int pthread_equal(pthread_t t1, pthread_t t2);

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

 4. 控制原語對比

 進程           線程
 fork      pthread_create
 exit      pthread_exit
 wait      pthread_join
 kill      pthread_cancel
 getpid    pthread_self 

5. 線程屬性

    本節作為指引性介紹,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;

    主要結構體成員:

  • 線程分離狀態
  • 線程棧大小(默認平均分配)
  • 線程棧警戒緩沖區大小(位於棧末尾) 參 APUE.12.3 線程屬性

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

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

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

    1)線程屬性初始化

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

    初始化線程屬性

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

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

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

    2)線程的分離狀態

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

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

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

    線程分離狀態的函數:

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

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

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

int 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()之類的函數,它們是使整個進程睡眠,並不能解決線程同步

的問題。

    3)線程的棧地址

    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:返回獲取的棧大小

    4)線程的棧大小

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

    函數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:返回線程的堆棧大小

    5)線程屬性控制示例

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 #include <stdlib.h>
 5 #include <string.h>
 6 
 7 void *thr(void *arg)
 8 {
 9     printf("I am a thread\n");
10     return NULL;
11 }
12 
13 int main()
14 {
15     pthread_attr_t attr;
16     pthread_attr_init(&attr);
17 
18     pthread_t tid;
19     pthread_create(&tid, &attr, thr, NULL);
20 
21     int ret;
22     if ((ret = pthread_join(tid, NULL)) > 0)
23     {
24         printf("join err: %d, %s\n", ret, strerror(ret));
25         return -1;
26     }
27     pthread_attr_destroy(&attr); //摧毀屬性
28 
29     return 0;
30 }
不分離
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 #include <stdlib.h>
 5 #include <string.h>
 6 
 7 void *thr(void *arg)
 8 {
 9     printf("I am a thread\n");
10     return NULL;
11 }
12 
13 int main()
14 {
15     pthread_attr_t attr;
16     pthread_attr_init(&attr);
17 
18     //設置屬性分離,不需要通過pthread_join回收
19     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
20 
21     pthread_t tid;
22     pthread_create(&tid, &attr, thr, NULL);
23 
24     /*
25     int ret;
26     if ((ret = pthread_join(tid, NULL)) > 0)
27     {
28         printf("join err: %d, %s\n", ret, strerror(ret));
29         return -1;
30     }*/
31     pthread_attr_destroy(&attr); //摧毀屬性
32 
33     sleep(1);
34 
35     return 0;
36 }
分離
 1 #include <pthread.h>
 2 
 3 #define SIZE 0x100000
 4 
 5 void *th_fun(void *arg)
 6 {
 7     while(1)
 8     {
 9         sleep(1);
10     }
11 }
12 
13 int main(void)
14 {
15     pthread_t tid;
16     int err, detachstate, i = 1;
17     pthread_attr_t attr;
18     void *stackaddr;
19     size_t stacksize;
20 
21     pthread_attr_init(&attr);
22     pthread_attr_getstack(&attr, &stackaddr, &stacksize);
23     pthread_attr_getdetachstate(&attr, &detachstate);
24 
25     if (detachstate == PTHREAD_CREATE_DETACHED)
26     {
27         printf("thread detached\n");
28     }
29     else if (detachstate == PTHREAD_CREATE_JOINABLE)
30     {
31         printf("thread join\n");
32     }
33     else
34     {
35         printf("thread unknown\n");
36     }
37 
38     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
39 
40     while (1) 
41     {
42 
43         stackaddr = malloc(SIZE);
44         if (stackaddr == NULL) 
45         {
46             perror("malloc");
47             exit(1);
48         }
49 
50         stacksize = SIZE;
51         pthread_attr_setstack(&attr, stackaddr, stacksize);
52         err = pthread_create(&tid, &attr, th_fun, NULL);
53         if (err != 0) 
54         {
55             printf("%s\n", strerror(err));
56             exit(1);
57         }
58         printf("%d\n", i++);
59     }
60 
61     pthread_attr_destroy(&attr);
62     
63     return 0;
64 }
綜合示例

    6)NPTL

  • 察看當前pthread庫版本getconf GNU_LIBPTHREAD_VERSION
  • NPTL實現機制(POSIX),Native POSIX Thread Library
  • 使用線程庫時gcc指定 –lpthread

 6. 線程使用注意事項

    主線程退出其他線程不退出,主線程應調用pthread_exit
    避免僵屍線程

  • pthread_join
  • pthread_detach
  • pthread_create指定分離屬性

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

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

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

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

練習:

1. 實現一個守護進程,每分鍾吸入一次日志,要去日志保存在$HOME/log/下,

  • 命名規則:程序名.yyyymm
  • 寫入內容格式:mm:dd hh:mi:ss程序名 [進程號]:消息內容

2. 實現多線程拷貝。(利用mmap映射區)


免責聲明!

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



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