一、整體大綱
二、線程相關
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的錯誤碼不保存在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 }
拓展思考:將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 }
調用該函數的線程將掛起等待,直到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_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 }
終止線程方式:
總結:終止某個線程而不終止整個進程,有三種方法:
- 從線程主函數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映射區)