Linux posix線程庫總結

   因爲歷史緣由,2.5.x之前的linux對pthreads沒有提供內核級的支持,因此在linux上的pthreads實現只能採用n:1的方式,也稱爲庫實現。html

線程的實現,經歷了以下發展階段:linux

  • LinuxThreads : Linux2.6以前
  • NPTL (Native Posix Thread Library) : RedHat負責,Linux2.6以後
  • NGPT (Next Generation Posix Thread): IBM負責,同NPTL同時開始研究的,可是最後被拋棄了(IBM......)

目前,pthreads的實現有3種方式:算法

(1)第一種,多對一,linux Threads,也就是庫實現。shell

  linux Threads,這是linux標準的的線程庫,可是與IEEE的POSIX不兼容. 在LinuxThreads中,專門爲每個進程構造了一個管理線程,負責處理線程相關的管理工做。當進程第一次調用pthread_create()建立一個線程的時候就會建立並啓動管理線程。而後管理線程再來建立用戶請求的線程。也就是說,用戶在調用pthread_create後,先是建立了管理線程,再由管理線程建立了用戶的線程。編程

(2)第二種,1:1模式。 NPTL多線程

 

  1:1模式適合CPU密集型的機器。咱們知道,進程(線程)在運行中會因爲等待某種資源而阻塞,多是I/O資源,也多是CPU。在多CPU機器上1:1模式是個很不錯的選擇。所以1:1的優勢就是,可以充分發揮SMP的優點。
這種模式也有它的缺點。因爲OS爲每一個線程創建一個內核線程,致使內核級的內存空間(IA32機器的4G虛存空間的最高1G)的大開銷。第二個缺點在於,在傳統意義上,在mutex互斥鎖和條件變量上的操做要求進入內核態,這是由於OS負責調度,它要爲線程的轉態轉換負全責。後面咱們會看到,Linux的NPTL庫避免了這個缺點,它的互斥鎖與條件變量的操做在用戶態完成。post

(3)第三種,M:N模式。測試

  這種模式試圖兼有上面2種模式的優勢。它要求一個分層的模型。比方說,某個進程內有4個線程,其中每2個線程對應一個內核線程。這樣,OS知道2個實體的存在,負責它們的調度。而在一個實體內有2個線程,這2個線程間的調度就是OS不加干涉、也不知道的了。
簡單的說,M:N模型的OS級調度上,跟1:1模型類似;線程間調度上,跟n:1模型類似。
優勢是,既能夠在進程內利用SMP的優點,又能夠節省系統空間內存的消耗,並且環境切換大多在用戶態完成。
缺點是顯然的:複雜。
spa

 

  Linux線程是經過進程來實現。Linux kernel爲進程建立提供一個clone()系統調用,clone的參數包括如 CLONE_VM, CLONE_FILES, CLONE_SIGHAND 等。經過clone()的參數,新建立的進程,也稱爲LWP(Lightweight process)與父進程共享內存空間,文件句柄,信號處理等,從而達到建立線程相同的目的。操作系統

 

普通進程和LWP在實現上的不一樣點是:

  • 普通進程,fork會調用clone,第三個參數flags不會包含CLONE_THREAD
  • LWP,pthread_create會調用clone,第三個參數flags會包含CLONE_THREAD(可能還有其餘幾個標誌),標示子進程和父進程同屬一個線程組(相同TGID)

線程和LWP是同一個東西,只是在用戶態,咱們管進程中每個執行序列爲「線程」,可是內核中它被稱爲LWP。由於內核上沒有線程的概念,CPU的調度是以進程爲單位的。

 

  在Linux 2.6以前,Linux kernel並無真正的thread支持,一些thread library都是在clone()基礎上的一些基於user space的封裝,所以一般在信號處理、進程調度(每一個進程須要一個額外的調度線程)及多線程之間同步共享資源等方面存在必定問題。Linux 2.6的線程庫叫NPTL(Native POSIX Thread Library)。POSIX thread(pthread)是一個編程規範,經過此規範開發的多線程程序具備良好的跨平臺特性。儘管是基於進程的實現,但新版的NPTL建立線程的效率很是高。一些測試顯示,基於NPTL的內核建立10萬個線程只須要2秒,而沒有NPTL支持的內核則須要長達15分鐘。

  在Linux中,每個線程都有一個task_struct。線程和進程可使用同一調度其調度。內核角度上來將LWP和Process沒有區別,有的僅僅是資源的共享。若是獨享資源則是HWP,共享資源則是LWP。而在真正內核實現的NPTL的實現是在kernel增長了futex(fast userspace mutex)支持用於處理線程之間的sleep與wake。futex是一種高效的對共享資源互斥訪問的算法。kernel在裏面起仲裁做用,但一般都由進程自行完成。NPTL是一個1×1的線程模型,即一個線程對於一個操做系統的調度進程,優勢是很是簡單。而其餘一些操做系統好比Solaris則是MxN的,M對應建立的線程數,N對應操做系統能夠運行的實體。(N<M),優勢是線程切換快,但實現稍複雜。

 

注:

(1)pthread線程庫--NPTL(Native POSIX Threading Library)

  在1:1核心線程模型中,應用程序建立的每個線程(也有書稱爲LWP)都由一個核心線程直接管理。OS內核將每個核心線程都調到系統CPU上,

所以,全部線程都工做在「系統競爭範圍」(system contention scope):線程直接和「系統範圍」內的其餘線程競爭。

(2)NGPT(Next Generation POSIX Threads)

  N:M混合線程模型提供了兩級控制,將用戶線程映射爲系統的可調度體以實現並行,這個可調度體稱爲輕量級進程(LWP:light weight process),LWP

再一一映射到核心線程。以下圖所示。OS內核將每個核心線程都調到系統CPU上,所以,全部線程都工做在「系統競爭範圍」。

 

添加:各階段線程的具體實現

LinuxThreads

在LinuxThreads中,專門爲每個進程構造了一個管理線程,負責處理線程相關的管理工做。當進程第一次調用pthread_create()建立一個線程的時候就會建立並啓動管理線程。而後管理線程再來建立用戶請求的線程。也就是說,用戶在調用pthread_create後,先是建立了管理線程,再由管理線程建立了用戶的線程。

爲了遵循POSIX對線程的一個規定:當"進程"收到一個致命信號(好比因爲段錯誤收到SIGSEGV信號), 進程內的線程所有退出,LinuxThreads的實現方法:

  1. 程序第一次調用pthread_create時, linuxthreads發現管理線程不存在, 因而建立這個管理線程. 這個管理線程是進程中的第一個線程(主線程)的兒子.
  2. 而後在pthread_create中, 會經過pipe向管理線程發送一個命令, 告訴它建立線程. 便是說, 除主線程外, 全部的線程都是由管理線程來建立的, 管理線程是它們的父親.
  3. 因而, 當任何一個子線程退出時, 管理線程將收到SIGUSER1信號(這是在經過clone建立子線程時指定的). 管理線程在對應的sig_handler中會判斷子線程是否正常退出, 若是不是, 則殺死全部線程, 而後自殺.
  4. 主線程是管理線程的父親, 其退出時並不會給管理線程發信號. 因而, 在管理線程的主循環中經過getppid檢查父進程的ID號, 若是ID號是1, 說明父親已經退出, 並把本身託管給了init進程(1號進程). 這時候, 管理線程也會殺掉全部子線程, 而後自殺.

容易發現,管理線程可能成爲多線程系統的瓶頸,線程建立和銷燬的開銷很大(須要IPC)。
更爲重要的是,LinuxThreads沒法知足Posix對線程的絕大多數規定,好比:

  • 查看進程列表的時候, 相關的一組task_struct應當被展示爲列表中的一個節點;
  • 發送給這個"進程"的信號(對應kill系統調用), 將被對應的這一組task_struct所共享, 而且被其中的任意一個"線程"處理;
  • 發送給某個"線程"的信號(對應pthread_kill), 將只被對應的一個task_struct接收, 而且由它本身來處理;
  • 當"進程"被中止或繼續時(對應SIGSTOP/SIGCONT信號), 對應的這一組task_struct狀態將改變;

NPTL

在linux 2.6中, 內核有了線程組的概念, task_struct結構中增長了一個tgid(thread group id)字段.
若是這個task是一個"主線程", 則它的tgid等於pid, 不然tgid等於進程的pid(即主線程的pid).

經過以下方式,解決了LinuxThreads不能兼容POSIX的問題:

  • 有了tgid, 內核或相關的shell程序就知道某個tast_struct是表明一個進程仍是表明一個線程, 也就知道在何時該展示它們, 何時不應展示(好比在ps的時候, 線程就不要展示了).
  • 爲了應付"發送給進程的信號"和"發送給線程的信號", task_struct裏面維護了兩套signal_pending, 一套是線程組共享的, 一套是線程獨有的。經過kill發送的信號被放在線程組共享的signal_pending中, 能夠由任意一個線程來處理; 經過pthread_kill發送的信號(pthread_kill是pthread庫的接口, 對應的系統調用中tkill)被放在線程獨有的signal_pending中, 只能由本線程來處理.
  • 當線程中止/繼續, 或者是收到一個致命信號時, 內核會將處理動做施加到整個線程組中.

NGPT

上面提到的兩種線程庫使用的都是內核級線程(每一個線程都對應內核中的一個調度實體), 這種模型稱爲1:1模型(1個線程對應1個內核級線程);
而NGPT則打算實現M:N模型(M個線程對應N個內核級線程), 也就是說若干個線程多是在同一個執行實體上實現的.

由於模型太複雜,貌似沒有實現出來全部預約功能,因此被放棄了。

 

參考連接

linux中的線程的實質和實現  http://particle128.com/posts/2014/05/linux-thread.html

Linux進程、線程模型,LWP,pthread_self() http://blog.csdn.net/tianyue168/article/details/7403693

相關文章
相關標籤/搜索