簡介 在傳統的作業系統中,進程擁有獨立的記憶體位址空間和一個用於控制的線程。但是,現在的情況更多的情況下要求在同一地址空間下擁有多個線程並發執行。因此線程被引入作業系統。 為什麼需要線程?
如果非要說是為什麼需要線程,還不如說為什麼需要進程中還有其它進程。這些進程中包含的其它迷你進程就是線程。 線程之所以說是迷你進程,是因為線程和進程有很多相似之處,比如線程和進程的狀態都有運行,就緒,阻塞狀態。這幾種狀態理解起來非常簡單,當進程所需的資源沒有到位時會是阻塞狀態,當進程所需的資源到位時但CPU沒有到位時是就緒狀態,當進程既有所需的資源,又有CPU時,就為運行狀態。
下面我們來看一個具體的例子:
就拿我寫部落格的LiveWriter來說,LiveWriter需要監聽我打字輸入的狀態,還需要每隔5分鐘對草稿進行自動儲存。假設如果這個進程只有一個線程的話,那麼當對草稿進行儲存時,因為此時需要訪問硬碟,而訪問硬碟的時間軸程是阻塞狀態的,這時我的任何輸入都會沒有響應,這種使用者體驗是無法接受的,或許我們可以通過鍵盤或者滑鼠的輸入去中斷儲存草稿的過程,但這種方案也並不討好。而使用多線程,每個線程僅僅需要處理自己那一部分應該完成的任務,而不用去關心和其它線程的衝突。因此簡化了編程模型。1所示。
圖1.兩條線程滿足各自的功能 更具體的說,線程的好處如下: 1.在很多程式中,需要多個線程互相同步或互斥的並行完成工作,而將這些工作分解到不同的線程中去無疑簡化了編程模型。 2.因為線程相比進程來說,更加的輕量,所以線程的建立和銷毀的代價變得更小。
3.線程提高了效能,雖然線程宏觀上是並行的,但微觀上卻是串列。從CPU角度線程並無法提升效能,但如果某些線程涉及到等待資源(比如IO,等待輸入)時,多線程允許進程中的其它線程繼續執行而不是整個進程被阻塞,因此提高了CPU的利用率,從這個角度會提升效能。 4.在多CPU或多核的情況下,使用線程不僅僅在宏觀上並行,在微觀上也是並行的。 這裡值得注意的是,上面的兩個線程如果改成兩個進程,那麼達不到所要的效果,因為進程有自己獨立的記憶體位址空間,而線程共用進程的記憶體位址空間。 經典執行緒模式 另一個看進程和線程的角度是進程模型基於兩類不同的概念:資源的組織和執行。在過去沒有線程的作業系統中,資源的組織和執行都是由進程完成的。但區分這兩者很多時候需要加以區分,這也是為什麼需要引入線程。
進程是用於組織資源的單位,進程將相關的資源群組織在一起,這些資源套件括:記憶體位址空間,程式,資料等,將這些以進程的形式組織起來可以使得作業系統管理這些資源更為容易。 而線程,是每一個進程中執行的一個條線。線程雖然共用進程中的大多數資源,但線程也需要自己的一些資源,比如:用於標識下一條執行指令的程式計數器,一些容納局部變數的寄存器,以及用於表示執行的曆史的棧。
總而言之:進程是組織資源的最小單位,而線程是安排CPU執行的最小單位。 其實在一個進程中多個線程並行和在作業系統中多個進程並行非常類似,只是線程共用的是地址空間,而進程共用的是實體記憶體,印表機,鍵盤等資源…… 每一個進程和線程所獨自佔有的資源如表1所示。 進程佔有的資源線程佔有的資源地址空間
全域變數
開啟的檔案
子進程
訊號量
賬戶資訊棧
寄存器
狀態
程式計數器
表1.進程和線程所獨佔的資源
其中,線程可以共用進程獨佔的資源。 我們常用的術語“多線程”一般指的是在同一個進程中多個線程的並發執行。2所示。
圖2.沒有多線程的系統一個進程只能由一個線程 在多線程的進程中,每個線程輪流使用CPU,因此實際上線程並不是並行的,但從宏觀上看,是並行的。 在多執行緒模式中,每一個進程初始建立時只有一個線程。這個線程可以通過調用系統的庫函數去建立其它線程。線程建立的線程並必須要為其指定地址,因為新的線程自動在建立它的地址空間內工作。雖然一個線程可以建立另一個線程,但通常來講,線程之間是並列的,並不存在層級關係。 當一個進程完成其工作後,可以通過調用系統庫函數進行銷毀。 作業系統實現線程的幾種模式在作業系統中,線程可以實現在使用者模式下,也可以實現在核心模式下,也可以兩者結合實現。 線程實現在使用者空間下 當線程在使用者空間下實現時,作業系統對線程的存在一無所知,作業系統只能看到進程,而不能看到線程。所有的線程都是在使用者空間實現。在作業系統看來,每一個進程只有一個線程。過去的作業系統大部分是這種實現方式,這種方式的好處之一就是即使作業系統不支援線程,也可以通過庫函數來支援線程。
在這種模式下,每一個進程中都維護著一個線程表來追蹤本進程中的線程,這個表中包含表1中每個線程獨佔的資源,比如棧,寄存器,狀態等,3所示。
圖3.在使用者空間中實現線程 這種模式當一個線程完成了其工作或等待需要被阻塞時,其調用系統過程阻塞自身,然後將CPU交由其它線程。 這種的模式的好處,首先,是在使用者空間下進行進程切換的速度要遠快於在作業系統核心中實現。其次,在使用者空間下實現線程使得程式員可以實現自己的線程調度演算法。比如進程可以實現記憶體回收行程來回收線程。還有,當線程數量過多時,由於在使用者空間維護線程表,不會佔用大量的作業系統空間。
有好處就有壞處,這種模式最致命的缺點也是由於作業系統不知道線程的存在,因此當一個進程中的某一個線程進行系統調用時,比如缺頁中斷而導致線程阻塞,此時作業系統會阻塞整個進程,即使這個進程中其它線程還在工作。還有一個問題是假如進程中一個線程長時間不釋放CPU,因為使用者空間並沒有時鐘中斷機制,會導致此進程中的其它線程得不到CPU而持續等待。 線程實現在作業系統核心中在這種模式下,作業系統知道線程的存在。此時線程表存在作業系統核心中,4所示。
圖4.線程在作業系統核心中實現 在這種模式下,所有可能阻塞線程的調用都以系統調用(System Call)的方式實現,相比在使用者空間下實現線程造成阻塞的運行時調用(System runtime call)成本會高出很多。當一個線程阻塞時,作業系統可以選擇將CPU交給同一進程中的其它線程,或是其它進程中的線程,而在使用者空間下實現線程時,調度只能在本進程中執行,直到作業系統剝奪了當前進程的CPU。
因為在核心模式下實現進程的成本更高,一個比較好的做法是另線程回收利用,當一個線程需要被銷毀時,僅僅是修改標記位,而不是直接銷毀其內容,當一個新的線程需要被建立時,也同樣修改被“銷毀”的線程其標記位即可。 這種模式下同樣還是有一些弊端,比如接收系統訊號的單位是進程,而不是線程,那麼由進程中的哪一個線程接收系統訊號呢?如果使用了表來記錄,那麼多個線程註冊則通過哪一個線程處理系統訊號? 混合模式 還有一種實現方式是將上面兩種模式進行混合,使用者空間中進程管理自己的線程,作業系統核心中有一部分核心層級的線程,5所示。
圖5.混合模式 在這種模式下,作業系統只能看到核心線程。使用者空間線程基於作業系統線程運行。因此,程式員可以決定使用多少使用者空間線程以及作業系統線程,這無疑具有更大的靈活性。而使用者空間線程的調度和前面所說的在使用者空間下執行實現線程是一樣的,同樣可以自訂實現。