蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》 寫在開頭 眾所周知,在電腦操作系統中,進程(Process)是一個很關鍵的概念,最本質的理解就是操作系統執行的一個應用程式(Application Program)。與每個進程相關的是地址空間(Address ...
蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》
寫在開頭
眾所周知,在電腦操作系統中,進程(Process)是一個很關鍵的概念,最本質的理解就是操作系統執行的一個應用程式(Application Program)。與每個進程相關的是地址空間(Address Space)。其中,描述的是從某個最小值的存儲位置(通常是0)到最大值的存儲位置的列表。在這個地址空間中,進程可以進行讀寫操作。地址空間中可以存放可執行程式,以及程式需要的數據和棧針。與每個進程相關的資源集合。通常包括寄存器(Registers),打開的文件清單,突發的系統報警,有關的進程清單和其他執行程式的信息。其中,寄存器主要包括程式計數器(Program Counter)和堆棧指針(Stack Pointer)。從一定程度上,我們可以把進程當作容納運行一個程式所有信息的一個容器(Container)。
操作系統中可以使用進程來描述一個程式的執行過程,進程擁有該程式的所有數據(包括一些I/O分配情況、記憶體分配情況等),也就是該程式的一個載體,所以進程有一個特點就是資源分配的單位,這一點十分重要。進程還有一個特點就是調度執行,交替執行以提高資源利用率。
操作系統管理進程(創建、切換進程、分配與回收等)開銷是很大的,比如進程創建時還需要創建PCB,分配記憶體獨立的內空間,建立映射表,創建資源,進程切換時還需要切換資源,如切換對應的記憶體映射表,進程退出時還需要釋放資源。
由此不難得出一個結論,每一個進程都有一個地址空間(Address Space)和一個控制線程(Control Thead)。但是,操作系統有了進程為何要出現線程呢?主要是因為,雖然進程任然是資源分配的單位,但是調度執行卻交給了線程,因為線程是在進程的內部,線程間的切換不用切換資源,不用切換映射表,只需要簡單的在進程內部切換一下PC指針和保存一些寄存器即可,這也就更輕量了(避免不了不同進程間的線程切換)。
基本概述
線程(Thread)既保留了併發執行的優點,也避免了進程切換的代價。
假設現在有一個網路伺服器,此時沒有線程的概念,該伺服器程式用到多個進程,如用一個進程監聽客戶端的請求,當客戶端連接上後就分派出(複製出一個子進程)一個進程給該用戶(每個進程都有獨立的資源),用於監聽該用戶發送的數據並處理(即多進程程式),此時設想一下,這多個進程切來切去,每次切換的時候都需要切換資源,是不是很耗費資源。
此時,引入了線程之後,網路伺服器這個程式是一個進程,進程用於承載該程式的資源,首先用進程中的一個線程監聽請求,每次連接客戶端都分配一個線程給用戶(多線程程式),此時處理器只需要在這幾個線程中切換即可,線程的切換不需要切換資源(進程時資源級的切換,線程是指令級的切換),那麼多個線程只需要共用進程中的資源即可,其運行速度和執行效率也得到了提升。
由此可見,操作系統引入線程後,調度和分派是線上程上完成的,但是某些活動會影響進程中的所有線程,因此這些活動必須在進程級對他們進行管理。如掛起操作會掛起所有線程,因為所有線程共用進程的用戶地址空間。引入了線程最關鍵的體現在以下兩個方面:
- 線程的創建、終止和調度更輕量
- 線程間的通信不進過內核,不需要用戶態->內核態的轉換
但是,同時也增加了程式的開發難度,如果開發者對於線程機制的掌握和認識不夠準確,也會陷入技術困惑。
線程模型
所有線程共用進程的狀態和資源,所以線程都駐留在同一塊地址空間中,並可訪問相同的數據。
對於有線程和無線程的區別,其中主要是體現在用戶棧和內核棧兩個關鍵:
- 用戶棧用於保存用戶進程的子程式間相互調用的參數、返回值以及局部變數等信息(保存普通方法的棧)
- 內核棧是程式發生系統調用時內核態調用方法時的棧;用戶地址空間則是進程的程式和數據存放的空間,線程是沒有自己的用戶地址空間的
一般來說,用戶棧和內核棧已經線上程中獨有,也證明瞭線程成為了任務調度的基本單位,這些線程都共用進程所持有的資源,線程式控制制塊中存放了寄存器的值、優先順序、線程狀態等信息。
在操作系統層面,線程也有“生老病死”,專業的說法叫有生命周期。雖然不同的開發語言對於操作系統線程進行了不同的封裝,但是對於線程的生命周期這部分,基本上是相通的。每一個線程基本都有如下特征:
- 類似進程,線程也有執行狀態(生命周期),因為線程也是一個執行過程
- 線程的上下文,線程切換時也需要進行保護現場
- 執行棧,保存系統調用時的一些參數和中間結果
- 少量的,線程私有的局部變數的存儲空間,不再擁有大量的存儲空間
- 與進程內其他線程共用的記憶體和資源的訪問
- 線程式控制制塊TCB,存放上下文切換的信息,同PCB
可以看出,對於有生命周期的事物,要學好和掌握它,思路非常簡單,只要能搞懂生命周期中各個節點的狀態轉換機制即可。
線程分類
線程分為用戶級線程(User-Level Thread,ULT)和內核級線程(Kernel-Level Thread,KLT),內核級線程又叫做輕量級進程(Light-Weight Process,LWP)。
用戶級線程(User-Level Thread,ULT)
在純ULT軟體中,管理線程的所有工作都是應用程式完成,內核意識不到線程的存在,線程完全是由線程庫提供的,創建、銷毀、調度線程、線程間傳遞消息等,還包括保存上下文都是由它管控的,如果可以的話我們自己也可以實現自己的線程庫,只要合理的組織線程即可。
但是用戶級線程所有的活動都發生在用戶空間和一個進程中,系統感知不到用戶級線程的存在,所以系統依舊是以進程的方式調度。
當線程1發生系統調用等阻塞了,此時系統就會認為該進程阻塞了,操作系統會把CPU時間片分配給其他進程,在此期間,根據線程庫維護的數據結構來看,線程1任然處於運行狀態,但在處理器執行的角度,線程2是不處於運行狀態的,也分不到時間片。
這也導致了用戶級線程一旦阻塞,就會阻塞進程中的所有線程,使得其他線程也得不到運行。使用用戶級線程(ULT)如下特點:
優點:
- 所有線程的管理都在一個進程的用戶空間中,線程的切換不需要內核模式特權,不需要系統調用,從而節省了用戶態到內核態轉換的開銷
- 線程的調度更靈活,可以為每個不同的應用程式量身定製更合適的調度演算法,因為這些調度演算法都可以自己實現,不需要更改操作系統底層的調度程式
- ULT可以在任何操作系統下運行,即便是不支持線程的操作系統也能實現,線程庫是供所有應用程式共用的一組應用級函數
缺點:
- 在執行一個系統調用時不僅僅是阻塞當前線程,還會阻塞進程中的所有線程
- ULT不能利用多處理技術,操作系統看不到線程,所以內核一次只能把一個進程分配給一個處理器,因此一個進程中的所有線程不能夠並行執行,只能夠併發執行,相當於一個進程內實現了多道程式設計
解決這兩個問題的方法有:
- 把應用程式寫出多進程程式,但是該方法消除了線程的主要優點
- 套管技術:把產生阻塞的系統調用轉化為一個非阻塞的系統調用
綜上所述,用戶級線程(User-Level Thread,ULT)適合計算密集型的,因為不需要IO操作 ,不會阻塞整個進程。
內核級線程(Kernel-Level Thread,KLT)
在KLT軟體中,管理線程的所有工作均由內核完成,應用級沒有線程管理代碼,只有一個到內核線程的API。
內核為進程級進程內的所有線程維護上下文信息,調度由內核基於線程完成。
該方法剋服了ULT的兩個缺點。首先,內核可以把一個進程中的線程分配個多個處理器中;其次,進程中的某個線程阻塞了,內核還可以調度同一個進程中的其他線程。
缺點是:在把控制權從一個線程傳送到另一個進程的線程時,需要切換到內核模式,開銷較大。
綜上所述,KLT併發性更好,適合I/O操作較多的程式。
混合線程(Hybrid-Approach Thread,HAT)
有些操作系統提供了ULT和KLT的混合體:線程創建完全在用戶空間中完成,線程的調度和同步也在應用程式中進行,一個應用程式中的多個用戶級線程會被映射到一些(小於等於用戶級線數)內核級線程上,進程和線程的比為 N:M,N<=M,ULT中比值為1:N,KLT為1:1
同一個應用程式中的多個線程可在多個處理器上並行的運行,某個引起阻塞的系統調用不會阻塞整個進程。
綜上所述,內核級線程(KLT)和用戶級線程(User-Level Thread,ULT)對比分析如下:
線程生命周期
一個線程的生命周期基本上可以這個“五態模型”來描述,主要分別是:初始狀態、可運行狀態、運行狀態、休眠狀態和終止狀態。其中:
- 初始狀態,指的是線程已經被創建,但是還不允許分配 CPU 執行。這個狀態屬於編程語言特有的,不過這裡所謂的被創建,僅僅是在編程語言層面被創建,而在操作系統層面,真正的線程還沒有創建。
- 可運行狀態,指的是線程可以分配 CPU 執行。在這種狀態下,真正的操作系統線程已經被成功創建了,所以可以分配 CPU 執行。
- 運行狀態:當有空閑的 CPU 時,操作系統會將其分配給一個處於可運行狀態的線程,被分配到 CPU 的線程的狀態就轉換成了運行狀態。
- 休眠狀態:運行狀態的線程如果調用一個阻塞的 API(例如以阻塞方式讀文件)或者等待某個事件(例如條件變數),那麼線程的狀態就會轉換到休眠狀態,同時釋放 CPU 使用權,休眠狀態的線程永遠沒有機會獲得 CPU 使用權。當等待的事件出現了,線程就會從休眠狀態轉換到可運行狀態。
- 終止狀態:線程執行完或者出現異常就會進入終止狀態,終止狀態的線程不會切換到其他任何狀態,進入終止狀態也就意味著線程的生命周期結束。
版權聲明:本文為博主原創文章,遵循相關版權協議,如若轉載或者分享請附上原文出處鏈接和鏈接來源。