「乾貨」linux線程庫詳解 附典型實例

在linux環境下的併發程序設計有兩個選擇,分別是多進程併發與多線程併發。

關於多進程與多線程編程之前已經有很多博文介紹過了,但關於線程庫的介紹卻很少。

在linux環境下當採用多線程編程時,需要在編譯的時候加上-lpthread(或-pthread) 以顯示鏈接該庫。之所以這樣是因為pthread並非Linux系統的默認庫,而是POSIX線程庫。而說起線程庫並不是一句話能夠說得清楚,因為這涉及到了Linux內核和線程庫的發展史。

起初在線程概念出現之後,並沒有在類unix操作系統中得到廣泛支持。比如在linux2.4以及以前版本,因為還沒有線程的概念,linux內核不知道什麼是線程,程序員也就沒有辦法在操作系統上創建線程。而後來隨著技術發展,線程帶來的好處被大家所認識,因為創建進程開銷的資源更多,且進程間的切換相比線程更慢。於是我們希望Linux能實現多線程編程,然而要修改一個操作系統並不是件容易的事情,於是採用的辦法是寫函數來實現,而不是去修改操作系統的內核。而這些函數也就是最初的線程庫,由於在linux內核中沒有線程的概念,因此這種線程是用進程來模擬的,實際上在不同的線程內調用getpid()函數,就會發現得到的值不同,因為它們在內核的進程鏈表中有不同的task_struct結構體來表示,有各自不同的進程標識符PID。因此這種線程也被稱為用戶級線程。雖然當時的線程庫已經和POSIX的標準非常接近了,但是在linux的線程實現版本和POSIX標準之間還是存在著細微的差別,最明顯的是關於信號處理部分,這些差別中的大部分都受底層linux內核的限制,而不是函數所能改變的。

許多項目都在研究如何才能改善linux對線程的支持,當然這種改善不僅僅是清除POSIX標準和linux具體實現之間的細微差別,還要增強linux線程的性能和刪除一些不需要的限制,這其中大部分工作集中在瞭如何將用戶級的線程映射到內核級的線程。

其中IBM公司的NGPT(Next Generation POSIX Threads),和Redhat公司的NPTL(Native POSIX Thread Library)通過修改linux內核來支持新的線程庫,兩者都極大地提升了性能。在2002年,NGPT項目組宣佈,由於不希望分化團隊,所以停止為NGPT添新功能,而只是繼續進行linux上的線程支持工作,從而有效地將他們的重擔放到了NPTL的身上。也因此NPTL成為了linux線程的新標準。NPTL有了很多優點:沒有使用管理線程。因為管理線程的一些需求,例如向作為進程一部分的所有線程發送終止信號,是並不需要的,因為內核本身就可以實現這些功能。內核還會處理每個線程堆棧所使用的內存的回收工作。它甚至還通過在清除父線程之前進行等待,從而實現對所有線程結束的管理,這樣可以避免殭屍進程的問題。

最後關於編譯參數-lpthread與-pthread其實是有區別的。主要在可移植性和安全性上。

在Linux中,pthread是作為一個單獨的庫存在的(libpthread.so),但是在其他Unix變種中卻不一定,比如在FreeBSD中是沒有單獨的pthread庫的,因此在FreeBSD中不能使用-lpthread來鏈接pthread,而使用-pthread則不會存在這個問題,因為FreeBSD的編譯器能正確將-pthread展開為該系統下的依賴參數。同樣道理,其他不同的變種也會有這樣那樣的區別,如果使用-lpthread,則可能在移植到其他Unix變種中時會出現問題,為了保持較高的可移植性,我們最好還是使用-pthread。在多數系統中,-pthread會被展開為-D_REENTRANT -lpthread,即是除了鏈接pthread庫外,還先定義了宏_REENTRANT。定義這個宏的目的,是為了打開系統頭文件中的各種多線程支持分支。比如,我們常常使用的錯誤碼標誌errno,如果沒有定義_REENTRANT,則實現為一個全局變量;若是定義了_REENTRANT,則會實現為每線程獨有,從而避免線程競爭錯誤。綜上所述,在編譯和鏈接時都使用-pthread 選項而不是傳統的-lpthread能夠保持向後兼容性和安全性。

Linux環境創建線程的實例。

#include

#include

#include

#include

#include

void *child_pthread(void *argc)

{

while(1)

{

printf("子線程process pid:%d,thread's id=%u\n",getpid(),(unsigned int)pthread_self());

sleep(2);

}

}

int main(void)

{

int ret;

pthread_t pid;

ret = pthread_create(&pid,NULL,child_pthread,NULL);

if(ret){

perror("pthread_create");

exit(0);

}

while(1)

{

printf("主線程process pid:%d,thread's id:%u\n",getpid(),(unsigned int)pthread_self());

sleep(2);

}

return 0;

}

在linux環境下創建線程採用pthread_create()

頭文件

#include

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

void *(*start_routine) (void *), void *arg);

返回值

若成功則返回0,否則返回出錯編號

編譯運行結果如下圖所示,其中編譯選項-pthread指定鏈接線程庫。運行結果主線程與子線程的進程id一致,線程id則不同。

「乾貨」linux線程庫詳解 附典型實例


分享到:


相關文章: