聊聊執行緒技術與執行緒實現模型

Float_Lu發表於2016-06-24

執行緒定義

什麼是執行緒?《POSIX Threads Programming》中有一段話對執行緒的定義進行描述:

A thread is defined as an independent stream of instructions that can be scheduled to run as such by the operating system.

執行緒可以被認為是一個可以被獨立排程的實體,這個實體共享程式的地址空間、檔案描述符、程式碼和資料,且擁有自己私有的棧、暫存器上下文、和程式計數器。

為什麼要執行緒

我們在 github 上面給開源專案提交程式碼的時候,按照 comment 格式都要寫 Motivation 這部分,我們今天討論執行緒這個存在,也要討論執行緒為什麼存在。

在很多應用中需要同時執行多個任務,這些任務大部分甚至全部都可以相互獨立的並行的執行。比如一個網路代理,傳統的實現是用一個程式作為監聽器來監聽網路埠,當有客戶端連線進來的時候,當前程式將會 fork 一個新的程式來處理客戶端的請求。這種體系結構不好的地方如下:

  • fork 系統呼叫對於作業系統來說是一個非常重的操作。
  • 每一個程式都有自己獨立的地址空間,程式間相互通訊必須要通過標準的 IPC 技術來實現,比如訊號量、共享記憶體,這些操作是非常昂貴的、嚴重影響系統效能。

執行緒的出現就是為了解決這些問題,執行緒之間擁有共享的程式空間用於共享資料、也有自己獨立的執行空間類似一個輕量級的程式。

使用者空間與核心空間

在理解使用者執行緒與核心執行緒之前、我們有必要了解一下使用者空間與核心空間。現代作業系統的地址空間主要基於虛擬地址空間機制設計,和實際實體記憶體大小沒關係,比如對於 32 位作業系統,它的定址空間為 2 的 32 次方也就是 4G,這裡的定址空間被稱為虛擬儲存空間。作業系統的核心是核心,獨立於普通應用程式,具有最高許可權,可以訪問底層硬體裝置以及受保護的空間,因此這部分包括驅動程式和作業系統。作業系統的設計者為了保證核心的安全,將使用者程式設計為只有一定許可權的程式,它不能夠操作核心以及硬體。作業系統將虛擬儲存空間劃分為兩部分,一部分是核心空間,一部分是使用者空間。針對 Linux 作業系統而言,最高的 1G 位元組供核心使用,稱為核心空間,較低的 3G 位元組供給各個程式使用,被稱為使用者空間。程式可以通過系統呼叫進入核心,Linux 核心由所有程式共享。使用者空間和核心空間示意圖如下:000835_2w4P_1759553

使用者空間及核心空間

使用者態與核心態

每個程式都擁有所有的虛擬地址空間,當程式執行使用者程式碼的時候是執行在使用者地址空間的,這時候 CPU 執行所需要的指令和資料都儲存在使用者空間,程式可以認為是指令 + 資料 + CPU,因此這個時候我們把這個狀態的程式叫做使用者程式。當使用者執行系統呼叫而陷入核心程式碼中執行的時候,當前程式執行的指令和資料都在核心空間,因此我們把這個狀態的程式叫核心程式。使用者程式和核心程式不是獨立的兩個程式的意思,而是程式執行的不同狀態。值得注意的是,使用者程式不能訪問核心虛擬地址空間,核心程式可以訪問全部的虛擬地址空間,因此使用者程式和核心程式進行資料交換隻能通過核心程式從使用者地址空間取資料,然後放入使用者地址空間。

系統呼叫涉及到程式從使用者態到核心態的切換(mode switch),這個時候涉及到的切換主要是暫存器上下文的切換,和通常所說的程式上下文切換不同,mode switch 的消耗相對要小很多。

使用者執行緒與核心執行緒

上面可以看出,使用者執行緒與核心執行緒的區別主要在於指令與資料執行於不同虛擬地址空間,使用者執行緒和核心執行緒也可以叫做使用者空間執行緒和核心空間執行緒。使用者執行緒由使用者程式碼支援,核心執行緒由作業系統核心支援。

執行緒上下文切換

執行緒上下文切換和執行緒模態切換不是一個維度的東東,執行緒上下文切換講的是多執行緒之間因為排程器的排程,而從一個執行緒正在被排程切換到另外一個執行緒被排程的事情。執行緒上下文切換必須要儲存執行緒執行的暫存器狀態、棧資訊、執行緒正文、資料等,因此相對模態切換是比較重的操作。

執行緒模型

執行緒模型在不同的作業系統下的實現通常有三種,每種模型都有其優點與缺點,下面我們來看看這三種執行緒模型。

使用者空間執行緒模型(M : 1)

一個多執行緒子系統有可能全部由使用者程式碼實現,這些執行緒的排程與切換全部發生在使用者地址空間,這種模型通常是由一個核心執行緒和多個使用者執行緒組成。典型的實現是基於 POSIX 執行緒 draft 4,OSF’DCE 是其中一種具體實現。一個使用者空間庫負責執行緒的建立、終止、排程與同步。這些執行緒對於作業系統核心是透明的。

這種模型的好處是執行緒上下文切換都發生在使用者空間,避免的模態切換(mode switch),從而對於效能有積極的影響。然而不好的地方是所有的執行緒基於一個核心排程實體即核心執行緒,這意味著只有一個處理器可以被利用,在多處理環境下這是不能夠被接受的,本質上,使用者執行緒只解決了併發問題,但是沒有解決並行問題。

還有一點,如果執行緒因為 I/O 操作陷入了核心態,核心態執行緒阻塞等待 I/O 資料,則所有的執行緒都將會被阻塞,使用者空間也可以使用非阻塞而 I/O,但是還是有效能及複雜度問題。000835_2w4P_1759553

使用者空間執行緒模型

核心空間執行緒模型(1:1)

對於使用者空間執行緒模型,所有的使用者執行緒都和特定的核心執行緒進行互動,而核心空間執行緒模型是每個使用者執行緒都和一個特定的核心執行緒進行互動,使用者執行緒和核心執行緒是 1:1 的關係。典型的實現是將每個使用者執行緒對映到一個核心執行緒上。

每個執行緒由核心排程器獨立的排程,所以如果一個執行緒阻塞則不影響其他的執行緒。然而,建立、終止和同步執行緒都會發生在核心地址空間,這可能會帶來較大的效能問題。在建立執行緒的時候核心必須要進行記憶體鎖的申請,並負責排程執行緒,而且每個執行緒都要消耗有限的核心資源,當大量的執行緒被建立的時候,體現的尤為明顯。值得誇獎的是,在多核處理器的硬體的支援下,核心空間執行緒模型支援了真正的並行,下面是核心空間模型示意圖:005851_eUxh_1759553

核心空間執行緒模型

核心使用者空間執行緒模型(M : N)

核心使用者空間執行緒模型中,核心執行緒和使用者執行緒的數量比為 M : N,因此也通常被叫做 M : N 執行緒模型,核心使用者空間綜合了前兩種的優點。

這種模型需要核心執行緒排程器和使用者空間執行緒排程器相互操作,本質上是多個執行緒被繫結到了多個核心執行緒上,這使得大部分的執行緒上下文切換都發生在使用者空間,而多個核心執行緒又可以充分利用處理器資源,模型圖如下:005851_eUxh_1759553

核心使用者空間執行緒模型

總結

最近在做 jvm 執行緒實現相關研究,發現 java 執行緒的問題其實是基於 C++ 和 pthread 執行緒庫的問題,往下深入,也就是作業系統對於執行緒實現的問題,這篇文章記錄了我對作業系統執行緒技術的認識和理解。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

聊聊執行緒技術與執行緒實現模型 聊聊執行緒技術與執行緒實現模型

相關文章