一、線程定義
什么是線程?《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 一個新的進程來處理客戶端的請求。這種體系結構不好的地方如下:
1、fork 系統調用對于操作系統來說是一個非常重的操作。
2、每一個進程都有自己獨立的地址空間,進程間相互通信必須要通過標準的 IPC 技術來實現,比如信號量、共享內存,這些操作是非常昂貴的、嚴重影響系統性能。
線程的出現就是為了解決這些問題,線程之間擁有共享的進程空間用于共享數據、也有自己獨立的運行空間類似一個輕量級的進程。
三、用戶空間與內核空間
在理解用戶線程與內核線程之前、我們有必要了解一下用戶空間與內核空間?,F代操作系統的地址空間主要基于虛擬地址空間機制設計,和實際物理內存大小沒關系,比如對于 32 位操作系統,它的尋址空間為 2 的 32 次方也就是 4G,這里的尋址空間被稱為虛擬存儲空間。操作系統的核心是內核,獨立于普通應用程序,具有最高權限,可以訪問底層硬件設備以及受保護的空間,因此這部分包括驅動程序和操作系統。操作系統的設計者為了保證內核的安全,將用戶進程設計為只有一定權限的程序,它不能夠操作內核以及硬件。操作系統將虛擬存儲空間劃分為兩部分,一部分是內核空間,一部分是用戶空間。針對 Linux 操作系統而言,最高的 1G 字節供內核使用,稱為內核空間,較低的 3G 字節供給各個進程使用,被稱為用戶空間。進程可以通過系統調用進入內核,Linux 內核由所有進程共享。用戶空間和內核空間示意圖如下:
四、用戶態與內核態
每個進程都擁有所有的虛擬地址空間,當進程運行用戶代碼的時候是運行在用戶地址空間的,這時候 CPU 運行所需要的指令和數據都保存在用戶空間,進程可以認為是指令 + 數據 + CPU,因此這個時候我們把這個狀態的進程叫做用戶進程。當用戶執行系統調用而陷入內核代碼中執行的時候,當前進程運行的指令和數據都在內核空間,因此我們把這個狀態的進程叫內核進程。用戶進程和內核進程不是獨立的兩個進程的意思,而是進程運行的不同狀態。值得注意的是,用戶進程不能訪問內核虛擬地址空間,內核進程可以訪問全部的虛擬地址空間,因此用戶進程和內核進程進行數據交換只能通過內核進程從用戶地址空間取數據,然后放入用戶地址空間。
系統調用涉及到進程從用戶態到內核態的切換(mode switch),這個時候涉及到的切換主要是寄存器上下文的切換,和通常所說的進程上下文切換不同,mode switch 的消耗相對要小很多。
五、用戶線程與內核線程
上面可以看出,用戶線程與內核線程的區別主要在于指令與數據運行于不同虛擬地址空間,用戶線程和內核線程也可以叫做用戶空間線程和內核空間線程。用戶線程由用戶代碼支持,內核線程由操作系統內核支持。
六、線程上下文切換
線程上下文切換和線程模態切換不是一個維度的東東,線程上下文切換講的是多線程之間因為調度器的調度,而從一個線程正在被調度切換到另外一個線程被調度的事情。線程上下文切換必須要保存線程執行的寄存器狀態、棧信息、線程正文、數據等,因此相對模態切換是比較重的操作。
七、線程模型
線程模型在不同的操作系統下的實現通常有三種,每種模型都有其優點與缺點,下面我們來看看這三種線程模型。
1、用戶空間線程模型(M : 1)
一個多線程子系統有可能全部由用戶代碼實現,這些線程的調度與切換全部發生在用戶地址空間,這種模型通常是由一個內核線程和多個用戶線程組成。典型的實現是基于 POSIX 線程 draft 4,OSF’DCE 是其中一種具體實現。一個用戶空間庫負責線程的創建、終止、調度與同步。這些線程對于操作系統內核是透明的。
這種模型的好處是線程上下文切換都發生在用戶空間,避免的模態切換(mode switch),從而對于性能有積極的影響。然而不好的地方是所有的線程基于一個內核調度實體即內核線程,這意味著只有一個處理器可以被利用,在多處理環境下這是不能夠被接受的,本質上,用戶線程只解決了并發問題,但是沒有解決并行問題。
還有一點,如果線程因為 I/O 操作陷入了內核態,內核態線程阻塞等待 I/O 數據,則所有的線程都將會被阻塞,用戶空間也可以使用非阻塞而 I/O,但是還是有性能及復雜度問題。
2、內核空間線程模型(1:1)
對于用戶空間線程模型,所有的用戶線程都和特定的內核線程進行交互,而內核空間線程模型是每個用戶線程都和一個特定的內核線程進行交互,用戶線程和內核線程是 1:1 的關系。典型的實現是將每個用戶線程映射到一個內核線程上。
每個線程由內核調度器獨立的調度,所以如果一個線程阻塞則不影響其他的線程。然而,創建、終止和同步線程都會發生在內核地址空間,這可能會帶來較大的性能問題。在創建線程的時候內核必須要進行內存鎖的申請,并負責調度線程,而且每個線程都要消耗有限的內核資源,當大量的線程被創建的時候,體現的尤為明顯。值得夸獎的是,在多核處理器的硬件的支持下,內核空間線程模型支持了真正的并行,下面是內核空間模型示意圖:
3、內核用戶空間線程模型(M : N)
內核用戶空間線程模型中,內核線程和用戶線程的數量比為 M : N,因此也通常被叫做 M : N 線程模型,內核用戶空間綜合了前兩種的優點。
這種模型需要內核線程調度器和用戶空間線程調度器相互操作,本質上是多個線程被綁定到了多個內核線程上,這使得大部分的線程上下文切換都發生在用戶空間,而多個內核線程又可以充分利用處理器資源,模型圖如下:
總結
最近在深入學習多線程的時候,大量學習了網上很多大神發表的文章,總結成上面的文章,歡迎大家指正。