Linux 下線程的理解


2017-04-03

最近深入研究了下Linux線程的問題,發現自己之前一直有些許誤解,特記之……


 

關於Linux下的線程,各種介紹Linux的書籍都沒有深入去解釋的,或許真的如書上所述,Linux本質上不存在線程的概念!在某種程度上的確是這樣,但是難道LInux就只有一種進程的東西么??答案肯定是否定的!下面咱們慢慢分析

說起Linux下的線程,的確不如windows下來的直接,windwos中進程和線程有着明確的區分,各自有自己代表的數據結構、操作API,進程享有資源和線程參加調度。一切都是那么自然。LInux下進程和線程的區分就沒有那么明顯了。從最初的用戶級線程模型,到后來內核級線程模型,Linux 對線程的支持雖然不如windows那么明顯,但是卻一直在努力。最初的用戶級線程這里就不在多說,那么時代內核還沒有加入對線程的支持,一切都是用戶程序在YY,算不上是內核支持的線程。Linux支持線程恐怕要從LInuxThreads線程庫被引入開始吧,后來基於LinuxThreadS線程庫的各種缺點,NPTL線程庫被引入進來。那么基於此,本篇文章介紹三個部分:內核級線程模型、LInuxThreas線程庫、NPTL線程庫。

1、內核級線程模型

關於線程模型,主要分為三種。用戶級線程(1:N),內核級線程(1:1),還有另外一種混合線程(M :N).因為種種原因,用戶級線程和混合線程都沒有進入內核,而正宗內核線程(1:1)確立了自己的正式地位。內核級線程,顧名思義是需要內核支持的,在早期用戶線程模型,內核不曉得有線程的存在,對於內核來說,自己按照進程進行資源分配和調度。而用戶程序在進程的基礎上,實現了多線程。這樣別的不說,線程的時間由用戶程序分配,一方面更加靈活,而另一方面卻大大增加了用戶程序的復雜度。且這種情況下,總體時間由內核分配給進程,用戶線程在平分進程得到的時間,宏觀上講,不同進程之間的線程沒有公平可言。再者,面對多處理器,多線程更蹩腳了,進程A在處理器1上運行,那么進程A的所有線程都必須在處理器1上運行,試問這樣的多處理器能發揮多大功效?而內核級線程就不同了,內核級線程是內核明確支持的,在內核中有其對應的數據結構,調度這點就不用用戶操心了,由內核管理。既然內核明確支持多線程,線程調度是可以實現公平的,面對多處理器,內核線程更具備用戶級線程所不具備的優勢。那么既然線程思想已經引入內核,為何還說Linux中線程思想不明確呢?主要是在內核中,線程對應的結構和進程一樣,都是對應task_struct結構,且進程創建和線程創建在底層的函數實現都是調用同一個AP Ido_fork,區別就在於參數不同,線程創建不會分配資源,會共享某個進程的資源。而實際上內核調度就是基於task_struct 結構的,這種情況下,你說調度到底是基於進程還是基於線程的??

2、LinuxThread線程庫

 LinuxThreas最先把多線程的概念引入Linux,但是其不適用於POSIX線程標准。但是Linux最初恐怕也沒有思考那么多,也許LinuxThreads發起者們僅僅看到了多線程相對於多進程的巨大好處:1、同一進程內線程的切換比進程切換要快得多,因此多線程的程序比多進程的程序運行要快。2、多線程可以讓切換見的間隙更小,從而實現更加真實的偽並行或者並行。基於上述目的,LinuxThreas便營運而生了。

LInuxthreads有以下特點:

1、管理線程的問題。每個進程擁有一個管理線程,進程內部線程的創建、銷毀、同步都經過管理線程處理。通過管理線程,實現了下面對於多線程的要求:

  • 系統必須有能力殺死整個進程。
  • 線程棧的回收必須在對應線程結束后執行,所以線程不能自己處理。
  • 將要終止的線程必須能夠被等待,從而不至於成為僵屍。
  • 線程數據區的回收需要對所有線程迭代執行,這個任務要管理線程完成。
  • 如果主線程需要調用pthread_exit(),而進程並沒有終止,主線程就睡眠,直到其他線程終止后,由管理線程喚醒主線程。

2、為了維護線程數據和內存,LinuxThreads使用進程地址空間的高位部分,即低於進程棧空間的部分。

3、基於信號機制實現線程同步。

4、LinuxThreads把每個線程實現為一個進程,擁有一個唯一的進程ID。

5、當進程收到終止信號,由管理線程負責用此信號殺死其他線程。

6、如果某個異步信號被發送,管理線程把信號傳遞給某個線程,如果該線程當前阻塞了該信號,則信號就是pending狀態。

7、內核調度器實現對線程的調度。

 基於上述特點,LinuxThreads的缺點就很明顯了:

1、其使用一個管理線程創建和協調進程中線程,這就增加了創建/銷毀線程的開銷。

2、既然其核心思想依賴於管理線程,那么必然要進行許多圍繞管理線程的上下文切換,從而影響擴展性和性能。

3、由於管理線程只有一個,只能運行在一個CPU,在多處理情況下,同步問題是一個大問題。

4、使用信號實現同步,基於信號性能問題,會增加操作響應延遲,由於不能專門對進程發送信號,也不遵循POSIX標准。

5、信號的處理時基於線程而不是基於進程,且信號的傳遞是串行化的,意味着如果某個線程阻塞了信號,信號不會被傳遞給其他線程直到該線程解除阻塞。

6、基於線程本質上是一個進程,那么那么同一進程下的線程user和groupID信息或許會不一致。對於setuid和setgid來講會成為問題。

7、如果進程中某個線程發生crash,那么dump文件僅僅針對該線程而不是針對其所屬進程(后期的LinuxThreads版本解決了這個問題)

8、由於每個線程也是一個進程,/proc文件系統會變得異常擁擠。且對於應用的線程數量也會受到限制。

9、線程數據的訪問依賴於棧地址,不能直接獲取,因此訪問線程數據的速度較慢,且用戶不能隨便指定棧的大小,以防發生沖突。

 

NPTL線程庫

NPTL或者成為Native POSIX Thread Library,是Linux下線程庫的全新實現,克服了LinuxThreads的缺點,且遵循POSIX標准。在可擴展性和性能方面,NPTL有了質的提升,不過NPTL也是基於1:1的內核線程模型實現的。

NPTL的設計目標如下:

1、應該遵守POSIX標准

2、在多處理器系統上應該工作良好。

3、NPTL線程庫應該和LInuxThreads二進制兼容。

4、NPTL應該充分利用NUMA的支持。

基於上述目標,NPTL的實現有如下特點:

 NPTL相對於LinuxThreads的提升,最最主要的兩個方面是1、取消了管理線程 2、增加了futex實現同步

取消管理線程的好處:

1)不使用管理線程,線程的創建/終止,發送信號操作均有內核負責,另外內核還負責回收線程的棧空間,在線程終止的時候,會讓父線程等待哦其所有孩子終止。

2)因為不適用管理線程,管理線程就不成為瓶頸,創建和終止效率更高,且減少了很多線程切換。

3)不使用管理線程,就不需要管理線程作為中介傳遞信號,在多處理器上可以更加方便的調度、通信。

futex帶來的好處:

futex成為快速互斥,用於線程之間的同步。LInuxThreads模型使用信號機制,該機制下勢必涉及線程的睡眠和喚醒,而睡眠和喚醒操作都是在內核完成的。futex實現了在用戶空間操作資源,有內核實現仲裁。可以顯著提高同步效率。

除此之外,信號處理可以基於進程,比如發送終止信號,可以直接針對進程發送,這樣進程中的所有線程均收到信號。而對於信號的傳遞,進程可用線程可實現接收,而之前如果線程阻塞了,那么信號就pending了。

/proc文件系統只列舉初始線程的信息。

資源使用報告基於進程而不是某個線程。

NPTL和LinuxThreads保持二進制兼容。

補充:

最后關於多線程在做下補充,現代不管是操作系統還是應用程序都是多線程的,許多人也都知道多線程可以讓程序運行更快、更流暢,但是為何多線程會對程序有這么大的影響,許多人可能就不太明白,我前些時間在知乎上看到一個討論多線程效率的問題,大牛們紛紛發表自己的意見,那么這里我就借花獻佛,在這里對於多線程做下介紹,當然這里僅僅涉及當前應用的線程模型-----內核線程模型。

1、多線程為何能提高程序運行效率

關於這個問題,大多數人應該都明白,線程把一個進程划分成一個個相對獨立的代碼段,且線程取代進程參與調度,即CPU按照線程來分配時間。從時間上來講,之前進程參與調度的時代,如果進程等待某個資源被阻塞,那么整個進程就都阻塞了,必須切換其他進程運行,而進程之間的切換開銷較大。多線程情況下,如果一個線程阻塞,在沒有同步關系的情況下完全可以調度其他的線程運行,粒度更小,相當於間隙更小,這樣程序運行勢必更快。但是這點並不是多線程高效的主要原因,其主要原因恐怕還要歸結於多處理器帶來的巨大優勢。現代計算機的CPU數目與日俱增,如何充分利用多處理器的優勢,讓處理器盡可能的忙起來呢?做個比喻,一個固定大小的容器,如果放置大小為A的不規則石頭,可放置10塊,如果把石頭敲碎,就可以放20塊,因為在大塊石頭下,有很多間隙,好比內存中的碎片,沒辦法利用,而縮小粒度的話,就可以填充的比較實。這里某種程度上多線程也是這個道理。且多處理下,線程的調度更加靈活,一個進程的不同線程可能同時在不同的處理器上運行,這樣就實現了真並行,程序運行自然更快。

2、單處理器下的多線程有意義么?

這就是知乎上這個問題的本質?對於這個問題,還真需要辯證的分析。先說沒有意義,在單處理器下,任意時刻只能由一個線程運行,此時即使是拿線程調度,一個進程的所有時間加起來最多也就等於進程調度方式下進程獲得的時間,且還不算線程調度下的切換時間。從這一個角度,多線程似乎沒有意義。

但是為何說起有意義呢?主要是因為理想和現實的差距。事實上,不管是線程還是進程,任意時刻絕大部分都是睡眠的,即現代計算機大多數是IO密集型,涉及IO必然要等待,進程調度下,進程一旦等待,必然要切換另一個進程執行,而在線程調度下,一個線程等待,可以調度另一個線程運行,,從這點看,多線程即使是在單處理器仍然是有意義的!當然在CPU密集型的系統就令說了!


免責聲明!

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



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