Linux線程的實現 & LinuxThread vs. NPTL & 用戶級內核級線程 & 線程與信號處理


另,線程的資源占用可見:http://www.cnblogs.com/charlesblc/p/6242111.html

進程 & 線程的很多知識可以看這里:http://www.cnblogs.com/charlesblc/p/6135666.html

 

線程一直是分系統級線程和用戶級線程,也就是所謂的 1:1線程模型和 1:n線程模型。注意Linux2.4版本之前pthread用的LinuxThread實現,和Linux2.5以后pthread用的NPTL(據說比較好支持了POSIX線程標准),都是系統級別的1:1線程模型,都是系統級線程。

具體可以參考這篇文章:http://blog.csdn.net/ixidof/article/details/24579879

LinuxThread的內核對應的管理實體就是進程,又稱LWP(輕量級進程),每個線程的pid是不一樣的

重要的是系統調用clone(),大家熟知的fork()函數就是調用clone()來實現父進程拷貝的從而創建一個新進程的。系統調用clone()里有一個flag參數,這個參數有很多的標志位指定了克隆時需要拷貝的東西,其中標志位CLONE_VM就是定義拷貝時是否使用相同的內存空間。fork()調用clone()時沒有設置CLONE_VM,所以在內核看來就是產生了兩個擁有不同內存空間的進程。而pthread_create()里調用clone()時設置了CLONE_VM,所以在內核看來就產生了兩個擁有相同內存空間的進程。所以用戶態創建一個新線程,內核態就對應生成一個新進程。

 

同步互斥

    內核沒有提供任何對線程的支持,當然也就沒有可供線程同步互斥使用的系統原語,但POSIX的線程標准里要求了諸多的互斥同步接口,怎么辦呢?

LinuxThread使用信號來模擬同步互斥,比如互斥鎖,大致過程我猜如下:新建互斥鎖的時候,在內核里把所有的進程mask掉一個特定信號,然后再kill()發出一個信號,等某個線程執行鎖定時,就用sigwait()查看是否有發出的信號,如果沒有就等待,有則返回,相當於鎖定。解鎖時就再kill()發出這個信號。那么LinuxThread使用的是哪幾個信號來模擬這個同步互斥的呢?有的文檔說是SIGUSR1和SIGUSR2,也有的說是某幾個實時信號,具體可以看對應線程庫的開發手冊。必須知道你所使用的線程庫內部使用哪幾個信號,因為如果你的多線程程序里也使用了這幾個信號的話,就會導致線程API工作混亂。

    從行分析就可以得出,LinuxThrea的同步互斥是用信號模擬完成的,所以效率不高且可能影響原有進程的信號處理,確實是個很大的缺陷。

 

信號處理

    LinuxThread的信號處理的行為可以說跟POSIX的標准是完全不一致的。因為信號的投遞過程是發生在內核的,而每個線程在內核都是對應一個個單獨的進程(不理解請看LinuxThread的創建線程一節),所以沒有內核支持,所以當你對一個進程發送一個信號后,只有擁有這個進程號的進程才有反應,而屬於這個進程的線程因為擁有不同的進程號而無法做出響應,從而LinuxThread無法做到跟POSIX定義的行為一致

 

線程管理

    這里不得不說到LinuxThread的一個特性,當你創建第一個線程時,也就會自動創建一個管理線程,這個過程對用戶是透明的。所以如果你還在使用LinuxThread線程庫,當你創建一個線程后ps的結果會是有三個相同的進程而不是兩個。這個管理線程的主要作用是管理線程的創建與終止,所以如果你把這個管理線程kill掉后,當你的某個線程退出后就會出現Zombie進程。另外,因為線程的創建與終止都要通過這個管理線程,在一個頻繁創建與終止線程的程序這個線程很可能成為性能的瓶頸。

 

 Native POSIX Thread Library(NPTL)

因為沒有內核支持的LinuxThread的線程實現的諸多缺陷,所以要想實現完全跟POSIX線程標准兼容的線程庫,重寫線程庫是必然的,內核的修改也勢在必行。有關NPTL實現也從線程創建,同步互斥及信號處理及線程管理幾個方面來說明。

 

創建線程

    NPTL同樣使用的是1 * 1模型,但此時對應內核的管理結構不再是LWP了。為了管理進程有進程組的概念,那內核要管理線程提出線程組的概念就是很自然的了。Linux內核只是在原來的進程管理結構新增了一個TGIP的字段,如下圖。當一個線程的PID等於TGID時,這個線程就是線程組長,其PID也就是這個線程組的進程號。線程組內的所有線程的TGID字段都指向線程組長的PID,當你使用getpid返回的都是TGID字段,而線程號返回的就是PID字段。那么NPTL下線程又是如何創建線程的呢?同樣是使用clone()系統調度,不過新的clone()調用的flag參數新增了一個標志位CLONE_THREAD,當這個標志位設置的時候新創建的行為就是創建一個線程,內核內部初始管理結構時把TGID指向調用者的PID,原來的PID位置填新線程號(也就是以前的進程號)。

    從上,LinuxThread因為在內核是一個LWP而產生的跟POSIX標准不兼容的錯誤都消除了。

 

注意圖中,用戶態內核態的划分。

 

同步與互斥

    從LinuxThread中的線程同步與互斥中可看到使用信號來模擬的缺點,所以內核增加一個新的互斥同步原語futex(fast usesapace locking system call),意為快速用戶空間系統鎖。因為進程內的所有線程都使用了相同的內存空間,所以這個鎖可以保存在用戶空間。這樣對這個鎖的操作不需要每次都切換到內核態,從而大大加快了存取的速度。NPTL提供的線程同步互斥機制都建立在futex上,所以無論在效率上還是咋對程序的外部影響上都比LinuxThread的方式有了很大的改進。具體futex的描述可以man futex。

 

信號處理

    此時因為同一個進程內的線程都屬於同一個進程,所以信號處理跟POSIX標准完全統一。當你發送一個SIGSTP信號給進程,這個進程的所有線程都會停止。因為所有線程內用同樣的內存空間,所以對一個signal的handler都是一樣的,但不同的線程有不同的管理結構所以不同的線程可以有不同的mask。后面這一段對LinuxThread也成立。

 

管理線程

    線程創建與結束的管理都由內核負責了,由LinuxThread的管理線程機制引出的問題已不復存在了。當然系統調度上仍是一個單獨的線程而不是多個線程組成一個進程為整體進行調度的。這跟POSIX的標准還是稍有不同,不過這一缺點看起來無傷大雅。 

注:從這一段可以看出:Linux是以線程為單位來調度。而POSIX貌似是要求以進程為單位來調度。

注:POSIX:Portable Operating System Interface 

 

另外,看到網上很多文章說,POSIX線程是混合模型,有用戶級和系統級線程,通過一個參數來選擇。等等。我是這樣理解的:

POSIX只是一個協議,各個系統的實現不一樣。我只知道LInux的線程是系統級線程,是內核參與調度的,是操作系統可見的。不管LinuxThread還是NPTL都是這樣的。

 

 

線程與信號處理

關於線程與信號處理的關系,還要再重點說一下。

參考這篇文章:http://www.cnblogs.com/cobbliu/p/5592659.html

 

 

 

 

 


免責聲明!

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



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