1. 程式人生 > >核心執行緒、輕量級程序、使用者執行緒三種執行緒概念解惑(執行緒≠輕量級程序)

核心執行緒、輕量級程序、使用者執行緒三種執行緒概念解惑(執行緒≠輕量級程序)

執行緒與程序概念

在現代作業系統中,程序支援多執行緒。

  • 程序是資源管理的最小單元;

  • 執行緒是程式執行的最小單元。

即執行緒作為排程和分配的基本單位,程序作為資源分配的基本單位

一個程序的組成實體可以分為兩大部分:執行緒集和資源集。程序中的執行緒是動態的物件;代表了程序指令的執行。資源,包括地址空間、開啟的檔案、使用者資訊等等,由程序內的執行緒共享。

執行緒概念的產生

傳統單執行緒程序的缺點

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

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

  3. 如果採用多程序的方法,則有如下問題:

    • fork一個子程序的消耗是很大的,fork是一個昂貴的系統呼叫,即使使用現代的寫時複製(copy-on-write)技術。
    • 各個程序擁有自己獨立的地址空間,程序間的協作需要複雜的IPC技術,如訊息傳遞和共享記憶體等。

多執行緒的優缺點

多執行緒的優點和缺點實際上是對立統一的。

支援多執行緒的程式(程序)可以取得真正的並行(parallelism),且由於共享程序的程式碼和全域性資料,故執行緒間的通訊是方便的。它的缺點也是由於執行緒共享程序的地址空間,因此可能會導致競爭,因此對某一塊有多個執行緒要訪問的資料需要一些同步技術。

執行緒的設計過程演變

在作業系統設計上,從程序演化出執行緒,最主要的目的就是更好的支援SMP以及減小(程序/執行緒)上下文切換開銷。

SMP機器上多執行緒的並行性

無論按照怎樣的分法,一個程序至少需要一個執行緒作為它的指令執行體,程序管理著資源(比如cpu、記憶體、檔案等等),而將執行緒分配到某個cpu上執 行。

一個程序當然可以擁有多個執行緒,此時,如果程序執行在SMP機器上,它就可以同時使用多個cpu來執行各個執行緒,達到最大程度的並行,以提高效率;同 時,即使是在單cpu的機器上,採用多執行緒模型來設計程式,正如當年採用多程序模型代替單程序模型一樣,使設計更簡潔、功能更完備,程式的執行效率也更 高,例如採用多個執行緒響應多個輸入,而此時多執行緒模型所實現的功能實際上也可以用多程序模型來實現,而與後者相比,執行緒的上下文切換開銷就比程序要小多 了,從語義上來說,同時響應多個輸入這樣的功能,實際上就是共享了除cpu以外的所有資源的。

執行緒模型–核心級執行緒和使用者級執行緒

針對執行緒模型的兩大意義,分別開發出了核心級執行緒使用者級執行緒兩種執行緒模型,分類的標準主要是執行緒的排程者在核內還是在核外。前者更利於併發使用多處理器的資源,而後者則更多考慮的是上下文切換開銷。

目前的實現策略

在目前的商用系統中,通常都將兩者結合起來使用,既提供核心執行緒以滿足smp系統的需要,也支援用線 程庫的方式在使用者態實現另一套執行緒機制,此時一個核心執行緒同時成為多個使用者態執行緒的排程者。

正如很多技術一樣,”混合”通常都能帶來更高的效率,但同時也 帶來更大的實現難度,出於”簡單”的設計思路,Linux從一開始就沒有實現混合模型的計劃,但它在實現上採用了另一種思路的”混合”。

線上程機制的具體實現上,可以在作業系統核心上實現執行緒,也可以在核外實現,後者顯然要求核內至少實現了程序,而前者則一般要求在核內同時也支援進 程。核心級執行緒模型顯然要求前者的支援,而使用者級執行緒模型則不一定基於後者實現。這種差異,正如前所述,是兩種分類方式的標準不同帶來的。

當核內既支援程序也支援執行緒時,就可以實現執行緒-程序的”多對多”模型,即一個程序的某個執行緒由核內排程,而同時它也可以作為使用者級執行緒池的排程 者,選擇合適的使用者級執行緒在其空間中執行。這就是前面提到的”混合”執行緒模型,既可滿足多處理機系統的需要,也可以最大限度的減小排程開銷。

絕大多數商業 作業系統(如Digital Unix、Solaris、Irix)都採用的這種能夠完全實現POSIX1003.1c標準的執行緒模型。在核外實現的執行緒又可以分為”一對一”、”多對 一”兩種模型,前者用一個核心程序(也許是輕量程序)對應一個執行緒,將執行緒排程等同於程序排程,交給核心完成,而後者則完全在核外實現多執行緒,排程也在用 戶態完成。後者就是前面提到的單純的使用者級執行緒模型的實現方式,顯然,這種核外的執行緒排程器實際上只需要完成執行緒執行棧的切換,排程開銷非常小,但同時因 為核心訊號(無論是同步的還是非同步的)都是以程序為單位的,因而無法定位到執行緒,所以這種實現方式不能用於多處理器系統,而這個需求正變得越來越大,因 此,在現實中,純使用者級執行緒的實現,除演算法研究目的以外,幾乎已經消失了。

Linux核心只提供了輕量程序的支援,限制了更高效的執行緒模型的實現,但Linux著重優化了程序的排程開銷,一定程度上也彌補了這一缺陷。目前 最流行的執行緒機制LinuxThreads所採用的就是執行緒-程序”一對一”模型,排程交給核心,而在使用者級實現一個包括訊號處理在內的執行緒管理機制。

三種執行緒概念——核心執行緒、輕量級程序、使用者執行緒

核心執行緒

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

核心執行緒只執行在核心態,不受使用者態上下文的拖累。

  • 處理器競爭:可以在全系統範圍內競爭處理器資源;

  • 使用資源:唯一使用的資源是核心棧和上下文切換時保持暫存器的空間

  • 排程:排程的開銷可能和程序自身差不多昂貴

  • 同步效率:資源的同步和資料共享比整個程序的資料同步和共享要低一些。

輕量級程序

輕量級程序(LWP)是建立在核心之上並由核心支援的使用者執行緒,它是核心執行緒的高度抽象,每一個輕量級程序都與一個特定的核心執行緒關聯。核心執行緒只能由核心管理並像普通程序一樣被排程。

a LWP runs in user space on top of a single kernel thread and shares its address space and system resources with other LWPs within the same process

輕量級程序由clone()系統呼叫建立,引數是CLONE_VM,即與父程序是共享程序地址空間和系統資源。

與普通程序區別:LWP只有一個最小的執行上下文和排程程式所需的統計資訊。

  • 處理器競爭:因與特定核心執行緒關聯,因此可以在全系統範圍內競爭處理器資源

  • 使用資源:與父程序共享程序地址空間

  • 排程:像普通程序一樣排程

輕量級執行緒(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使用的執行緒庫

LinuxThreads是使用者空間的執行緒庫,所採用的是執行緒-程序1對1模型(即一個使用者執行緒對應一個輕量級程序,而一個輕量級程序對應一個特定的核心執行緒),將執行緒的排程等同於程序的排程,排程交由核心完成,而執行緒的建立、同步、銷燬由核外執行緒庫完成(LinuxThtreads已繫結到 GLIBC中發行)。

在LinuxThreads中,由專門的一個管理執行緒處理所有的執行緒管理工作。當程序第一次呼叫pthread_create()建立執行緒時就會先 建立(clone())並啟動管理執行緒。後續程序pthread_create()建立執行緒時,都是管理執行緒作為pthread_create()的呼叫者的子執行緒,通過呼叫clone()來建立使用者執行緒,並記錄輕量級程序號和執行緒id的對映關係,因此,使用者執行緒其實是管理執行緒的子執行緒。

LinuxThreads只支援排程範圍為PTHREAD_SCOPE_SYSTEM的排程,預設的排程策略是SCHED_OTHER。
使用者執行緒排程策略也可修改成SCHED_FIFO或SCHED_RR方式,這兩種方式支援優先順序為0-99,而SCHED_OTHER只支援0。

  • SCHED_OTHER 分時排程策略,

  • SCHED_FIFO 實時排程策略,先到先服務

  • SCHED_RR 實時排程策略,時間片輪轉

SCHED_OTHER是普通程序的,後兩個是實時程序的(一般的程序都是普通程序,系統中出現實時程序的機會很少)。SCHED_FIFO、 SCHED_RR優先順序高於所有SCHED_OTHER的程序,所以只要他們能夠執行,在他們執行完之前,所有SCHED_OTHER的程序的都沒有得到 執行的機會

小結:

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