標籤:

Linux下調用pthread庫創建的線程是屬於用戶級線程還是內核級線程?求大神指教?


linux的pthread(如果沒動過手腳)都是kernel thread。


這個事情,還真不是一句話就能回答的,因為涉及到Linux和編譯器的版本

關於線程的概念不多說了,內核級和用戶級線程的定義網上也有,簡單的說:內核級就是操作系統內核支持,用戶級就是函數庫實現(也就是說,不管你操作系統是不是支持線程的,我都可以在你上面用多線程編程)。

好了,那麼,我們首先明白一件事:不管Linux還是什麼OS,都可以多線程編程的,怎麼多線程編程呢?程序員要創建一個線程,當然需要使用xxx函數,這個函數如果是操作系統本身就提供的系統函數,當然沒問題,操作系統創建的線程,自然是內核級的了。

如果操作系統沒有提供「創建線程」的函數(比如Linux 2.4及以前的版本,因為Linux剛誕生那時候,還沒有「線程」的概念,能處理多「進程」就不錯了),當然你程序員也沒辦法在操作系統上創建線程。所以,Linux 2.4內核中不知道什麼是「線程」,只有一個「task_struct」的數據結構,就是進程。那麼,後來隨著科學技術的發展,大家提出線程的概念,而且,線程有時候的確是個好東西,於是,我們希望Linux能加入「多線程」編程。

要修改一個操作系統,那是很複雜的事情,特別是當操作系統越來越龐大的時候。怎麼才能讓Linux支持「多線程」呢?

首先,最簡單的,就是不去動操作系統的「內核」,而是寫一個函數庫來「模擬」線程。也就是說,我用C寫一個函數,比如 create_thread,這個函數最終在Linux的內核里還是去調用了創建「進程」的函數去創建了一個進程(因為OS沒變嘛,沒有線程這個東西)。 如果有人要多線程編程,那麼你就調用 這個create_thread 去創建線程吧,好了,這個線程,就是用庫函數創建的線程,就是所謂的「用戶級線程」了。等等,這是神馬意思?赤裸裸的欺騙?也不是。

為什麼不是?因為別人給你提供了這個線程函數,你創建了「線程」,那麼,你的線程(雖然本質上還是進程)就有了「線程」的一些「特徵」,比如可以共享變數啊什麼的,咦?那怎麼做到的?當然有一套機制,反正人家給你做好了,你用就行了。

這種欺騙自然是不「完美」的,有線程的「一些」特徵,但不能完全符合理論上的「線程」的概念(POSIX的要求),比如,這種多線程不能被分配到多核上,用戶創建的N個線程,對於著內核裡面其實就一個「進程」,導致調度啊,管理啊麻煩.....

為什麼要採用這種「模擬」的方式呢?改內核不是一天兩天的事情,先將就用著吧。內核慢慢來改。

怎麼干改內核這個艱苦卓越的工作?Linux是開源、免費的,誰願意來干這個活?有兩家公司參與了對LinuxThreads的改進(向他們致敬):IBM啟動的NGTP(Next Generation POSIX Threads)項目,以及紅帽Redhat公司的NPTL(Native POSIX Thread Library),IBM那個項目,在2003年因為種種原因放棄了,大家都轉到NPTL這個項目來了。

最終,當然是成功了,在Linux 2.6的內核版本中,這個NPTL項目怎麼做的呢?並不是在Linux內核中加了一個「線程」,仍然和原來一樣,進程(其實,進程線程就是個概念,對於計算機,只要能高效的實現這個概念就行,程序員能用就OK,管它究竟怎麼實現的),不過,用的clone實現的輕量級進程,內核又增加了若干機制來保證線程的表現和POSIX相同,最關鍵的一點,用戶調用pthread庫創建的一個線程,會在內核創建一個「線程」,這就是所謂的1:1模型。所以,Linux下,是有「內核級」線程的,網上很多說Linux是用戶級線程,都是不完整的,說的Linux很早以前的版本(現在Linux已經是4.X的版本了)。

還有個 pthread 的問題,pthread是個線程函數庫,他提供了一些函數,讓程序員可以用它來創建,使用線程。那麼問題是,這個函數庫裡面的函數,比如 pthread_create 創建線程的函數,他是怎麼實現的呢?他如果是用以前的方法,那程序員用它來創建的線程,還是「用戶級」線程;如果它使用了NPTL方式創建線程,那麼,它創建的線程,就是「內核級」線程。

OK,結論,如果你 1:使用2.6的內核的系統平台,2:你的gcc支持NPTL (現在一般都支持),那麼你編譯出來的多線程程序,就是「內核級」線程了。

所以,現在回答問題,只要你不是很古董級的電腦,Linux下用pthread創建的線程是「內核級線程」

最後,這NPTL也並不是完美的,還有一些小問題,像有一些商業操作系統,可以實現混合模型,如1:1,N:M等(就是內核線程和用戶線程的對應關係),這就強大了,Linux仍有改進的空間


檢查 /proc/[pid]/task,這裡有對應的就是內核級。


NPTL實現中,用戶創建的線程和內核中調度實體的關係是1:1。其他線程庫的實現,可以支持M:N。

在NPTL實現中,用戶態的線程,在內核態有自己的進程描述符task_struct,是獨立的調度實體。


進程,輕量級進程,內核線程,用戶線程的區別關係

在現代操作系統中,進程支持多線程。進程是資源管理的最小單元;而線程是程序執行的最小單元。一個進程的組成實體可以分為兩大部分:線程集合資源集。進程中的線程是動態的對象;代表了進程指令的執行。資源,包括地址空間、打開的文件、用戶信息等等,由進程內的線程共享。

線程有自己的私有數據:程序計數器,棧空間以及寄存器。

Why Thread?(傳統單線程進程的缺點)

1. 現實中有很多需要並發處理的任務,如資料庫的伺服器端、網路伺服器、大容量計算等。

2. 傳統的UNIX進程是單線程的,單線程意味著程序必須是順序執行,不能並發;既在一個時刻只能運行在一個處理器上,因此不能充分利用多處理器框架的計算機。

3. 如果採用多進程的方法,則有如下問題:
a. fork一個子進程的消耗是很大的,fork是一個昂貴的系統調用,即使使用現代的寫時複製(copy-on-write)技術。
b. 各個進程擁有自己獨立的地址空間,進程間的協作需要複雜的IPC技術,如消息傳遞和共享內存等。

多線程的優缺點

多線程的優點和缺點實際上是對立統一的。

支持多線程的程序(進程)可以取得真正的並行(parallelism),且由於共享進程的代碼和全局數據,故線程間的通信是方便的。它的缺點也是由於線程共享進程的地址空間,因此可能會導致競爭,因此對某一塊有多個線程要訪問的數據需要一些同步技術。

三種線程——內核線程、輕量級進程、用戶線程

內核線程

內核線程就是內核的分身,一個分身可以處理一件特定事情。這在處理非同步事件如非同步IO時特別有用。內核線程的使用是廉價的,唯一使用的資源就是內核棧和上下文切換時保存寄存器的空間。支持多線程的內核叫做多線程內核(Multi-Threads kernel )。

輕量級進程[*]

輕量級線程(LWP)是一種由內核支持的用戶線程。它是基於內核線程的高級抽象,因此只有先支持內核線程,才能有LWP。每一個進程有一個或多個LWPs,每個LWP由一個內核線程支持。這種模型實際上就是恐龍書上所提到的一對一線程模型。在這種實現的操作系統中,LWP就是用戶線程。

由於每個LWP都與一個特定的內核線程關聯,因此每個LWP都是一個獨立的線程調度單元。即使有一個LWP在系統調用中阻塞,也不會影響整個進程的執行。

輕量級進程具有局限性。首先,大多數LWP的操作,如建立、析構以及同步,都需要進行系統調用。系統調用的代價相對較高:需要在user mode和kernel mode中切換。其次,每個LWP都需要有一個內核線程支持,因此LWP要消耗內核資源(內核線程的棧空間)。因此一個系統不能支持大量的LWP。

註:

1 LWP的術語是借自於SVR4/MP和Solaris 2.x。

2 有些系統將LWP稱為虛擬處理器。

3 將之稱為輕量級進程的原因可能是:在內核線程的支持下,LWP是獨立的調度單元,就像普通的進程一樣。所以LWP的最大特點還是每個LWP都有一個內核線程支持。

用戶線程

LWP雖然本質上屬於用戶線程,但LWP線程庫是建立在內核之上的,LWP的許多操作都要進行系統調用,因此效率不高。而這裡的用戶線程指的是完全建立在用戶空間的線程庫,用戶線程的建立,同步,銷毀,調度完全在用戶空間完成,不需要內核的幫助。因此這種線程的操作是極其快速的且低消耗的。

上圖是最初的一個用戶線程模型,從中可以看出,進程中包含線程,用戶線程在用戶空間中實現,內核並沒有直接對用戶線程進行調度,內核的調度對象和傳統進程一樣,還是進程本身,內核並不知道用戶線程的存在。用戶線程之間的調度由在用戶空間實現的線程庫實現。

這種模型對應著恐龍書中提到的多對一線程模型,其缺點是一個用戶線程如果阻塞在系統調用中,則整個進程都將會阻塞。

加強版的用戶線程——用戶線程+LWP

這種模型對應著恐龍書中多對多模型。用戶線程庫還是完全建立在用戶空間中,因此用戶線程的操作還是很廉價,因此可以建立任意多需要的用戶線程。操作系統提供了LWP作為用戶線程和內核線程之間的橋樑。LWP還是和前面提到的一樣,具有內核線程支持,是內核的調度單元,並且用戶線程的系統調用要通過LWP,因此進程中某個用戶線程的阻塞不會影響整個進程的執行。用戶線程庫將建立的用戶線程關聯到LWP上,LWP與用戶線程的數量不一定一致。當內核調度到某個LWP上時,此時與該LWP關聯的用戶線程就被執行。

小結:

很多文獻中都認為輕量級進程就是線程,實際上這種說法並不完全正確,從前面的分析中可以看到,只有在用戶線程完全由輕量級進程構成時,才可以說輕量級進程就是線程。

參考資料:進程,輕量級進程,內核線程,用戶線程的區別關係


Linux里,用戶代碼通過pthread庫創建線程的過程雖然看似是用戶在創建「用戶級線程」,實際上是pthread_create暗中調用了clone系統調用,由操作系統幫忙創建內核級線程的過程,因而不能稱作用戶級線程。

一般的用戶級線程指的是創建、調度和銷毀都不經過操作系統,他們的創建與調度由庫來實現,操作系統是無法感知多個用戶級線程的。

但是在Linux中使用pthread_create創建的「用戶線程」準確講應該叫輕量級進程。每使用pthread_create創建一次輕量級進程,OS都會相應地為應用程序生成一個可供內核調度的實體,暫且稱作內核線程吧。這就是前面有人講的Linux線程管理中的NPTL 1:1模型,即1個輕量級線程對應一個內核級線程。所以這個「輕量級進程」並不是上一段里講的用戶級線程。


pthread是1:1線程模型,實現是NPTL庫


那到底是內核級線程還是用戶級線程?有沒一個權威說法


主要是內核調度的肯定是內核級的


推薦閱讀:

怎麼寫規範、風格良好的代碼?
數學一般的人適合學習編程嗎?
在PowerShell中用命令運行.py文件 有黑色框閃了了一下 然後PowerShell直接跳下一行新的命令輸入了 求解?
熱愛C語言的我該何去何從?
如何有效閱讀Github上開源項目代碼?

TAG:編程 | Linux |