文章目錄:
一、進程、線程、協程、管程
(一)進程
【1.引入進程:】
傳統的程序無法併發,爲了實現多個程序併發運行,引入了進程的概念。
【2.PCB:】
因爲併發程序運行時需要時CPU時間片輪轉,需要保存一些現場信息,以便下次恢復CPU現場繼續執行,因此我們需要一種數據結構標識一個運行中的程序,稱爲PCB進程控制塊,保存了進程的各類信息。
【3.進程間切換:】
- 虛擬地址空間的切換,最耗時,從task_struct中獲取到頁表的物理地址,建立映射。
- 進程內核棧的切換。
- 進程用戶堆棧的切換和寄存器的保存。
【4.進程基本概念:】
- 進程是操作系統對一個正在運行的程序的一種抽象結構。
- 進程是指在操作系統中能獨立運行並作爲資源分配的基本單位。
- 進程是由一組機器指令,數據和堆棧等組成的能獨立運行的活動實體。
- 操作系統可以同時運行多個進程,多個進程直接可以併發執行和交換信息。
- 進程的運行和管理需要一定的資源,如PCB,CPU等。
- 進程極大的提高了資源利用率和系統吞吐量
(二)線程
【1.引入線程:】
建立進程、管理進程和進程併發之間的切換都會帶來時間效率上的開銷,開銷較大,但是切換過程效率較低。
爲了提高效率,操作系統引入了線程的概念,進程是操作系統進程資源和調用的基本單位,線程的引入分離了這種職責,線程是進程的實際運作單位,是執行調度的基本單位,同時擁有部分資源,這樣,我們一個進程裏面可以創建多個線程實現併發,節省了進程切換的開銷。
【2.線程間切換:】
線程共享進程的地址空間和內核棧,所以線程切換時,只會保存線程的棧信息和少量寄存器即可,因此線程之間切換的開銷遠小於進程。
【3.線程基本概念:】
- 線程實際上是進程內部的一條執行序列,即執行流。執行序列是指一組有序指令假數據的集合,以函數單位。
- 在Linux系統中,沒有線程的概念,內核看到了比傳統進程更加輕量化地PCB,故線程是一種輕量級的進程。
- Linux 內核的實現中,並沒有單獨的線程概念,線程僅僅被視爲與進程共享資源的特殊進程,clone 系統調用中傳入 CLONE_VM,即共享父進程地址空間。
- 線程在進程內部運行,共享進程的地址空間,每一個線程都擁有一個獨立的計數器,棧,寄存器。
- 進程調度的對象是線程,而不是進程。
- 一個進程至少有一個執行線程。
- PCB管理進程,Linux上沒有真正意義上地線程控制塊TCB,而是用進程控制塊來模擬線程TCB。
【4.線程的優點:】
- 創建線程需要的資源比進程小得多。
- 線程切換的開銷小,不必進行虛地址空間切等切換,只需要進行棧,寄存器保存即可。
- 線程佔用資源少,併發高。
- 可以實現計算密集型應用,可以將計算分解到多個線程中實現。
- 可以實現I/O密集型應用,爲了提高性能,將I/O操作重疊,線程可以同時等待不同的I/O操作。
【5.線程的缺點:】
- 性能方面:如果有大量的線程,操作系統需要在它們之間來回切換,會影響性能。
- 安全方面:線程的共享性大,很容易引發線程安全問題,對共享資源的控制如果不當,那麼就會出現死鎖等安全問題。
- 控制方面:線程是不可控的,所以必須對線程進行同步控制,實現線程同步。
- 編程方面:實現多個線程比單線程困難,編程難度大。
(三)進程&&線程
線程和進程的區別,總結如下:
- 根本區別:進程是系統進行資源分配的基本單位; 線程是任務調度和執行的基本單位。
- 內存層面:操作系統每創建一個進程,都會給這個進程分配不同的地址空間,來存儲程序所佔用的資源,進程相互獨立存在; 線程共享進程的地址空間,操作系統制位它分配很小一部分內存,TLS(在一個線程內部的各個函數調用都能訪問、但其它線程不能訪問的變量(被稱爲線程局部靜態變量),線程局部存儲,用來存儲線程獨有的資源,整體上它是共享進程的資源,所以棧獨立,其他空間共享。
- 開銷方面:進程切換的開銷很大,需要地址空間的切換,進程內核棧的切換,進程用戶堆棧以及寄存器的切換; 線程可以看做是輕量級進程,共享進程地址空間,它的切換僅僅是線程棧和PC寄存器的保存切換,因此線程切換的開銷小。
- 包含關係:一個進程至少有一個線程,用於執行程序,稱爲主線程,或者單線程程序,因此,線程是包含在進程中的。
- 通信方面:進程之間的通信需要通過進程間通信(IPC),而同一個進程的個線程之間可以直接通過傳遞地址或者全局變量的方式傳遞變量。
- 安全方面:進程之間不存在安全問題;而同一個進程的線程因爲共享性大,所以存在安全問題。
我們列表總結:
進程 | 線程 | |
---|---|---|
本質 | 系統進行分配資源的最小單位 | CPU調度的最小單位 |
包含關係 | 一個進程包含多個線程 | 一個線程只屬於一個進程 |
開銷 | 大 | 小 |
切換效率 | 低 | 高 |
存在形式 | 相互獨立 | 棧獨立,其他空間共享 |
通信 | 使用IPC,藉助外部手段 | 內部直接通過共享內存通信 |
安全問題 | 不存在 | 存在 |
(四)多進程
我們可以通過fork創建子進程,exec替換進程這種方式構建多個線程,即多個程序在運行,我們稱爲多線程。
【1.多進程的優點:】
- 每個進程相互獨立,不影響主程序的穩定性,子進程奔潰也沒關係。
- 通過增加CPU,可以實現並行,擴充性能。
- 多進程間使用同步機制少,所以性能較高。
- 每個子進程都有2GB地址空間和相關資源,總體能夠達到的性能上限非常大。
【2.多進程的缺點:】
- 邏輯控制複雜,需要創建,替換進程兩個步驟,和主程序交互。
- 進程間通信不方便,需要跨進程邊界,可以通信小數據,一旦密集運算,大數據,那麼開銷很大。
(五)多線程
多線程就是指一個進程中同時有多個線程正在執行。
多線程是異步的,多個線程同時執行不代表真正的同時運行,而是一種併發形式,系統不斷在各個線程之間來回切換,切換速度快,所以給我們一種多線程一起運行的錯覺
【1.多線程的優點:】
- 多線程可以同時執行多個操作,併發性高。
- 無需跨進程邊界,可以提高程序的效率。
- 所有線程可以直接共享內存和變量進行通信。
- 線程消耗的資源比進程少。
【2.多線程的缺點:】
- 線程之間的同步和加鎖控制麻煩。
- 一個線程的崩潰,如掛起,終止等操作可能影響整個程序的穩定性。
- 線程能夠提高的性能有限,隨着線程的增多,會耗費大量的系統資源,內存。
- 大量線程之間的切換影響性能。
- 對線程控制不當會引發線程安全問題。
(六)多進程&&多線程
下面的例子從知乎-pansz上轉載的,非常通俗地解釋了這幾個概念:桌子就是進程,菜就是臨界資源。
- 單進程單線程::一個人在一個桌子上喫菜
- 單進程多線程:多個人在同一個桌子上喫菜
- 多進程單線程:多個人每個人在自己的桌子上喫菜
多線程的問題是:多個人在一個桌子上同時喫一道菜的時候容易發生爭搶,如兩個人同時夾着一道菜,這就是臨界資源會發生衝突爭奪。
多進程的問題是:坐在不同的桌子上,說話不方面,即通信不方便,需要藉助外力去說話,如聲音要放大,移動板凳,找個人傳話等。
【對於windows系統來說】,【開桌子】的開銷很大,所以windows鼓勵大家在一個桌子上喫菜,因此windows多線程重點是學習多線程同步控制和安全控制。
【對於Linux系統來說】,【開桌子】的開銷很小,所以Linux鼓勵大家儘量每個人都開自己的桌子喫菜,因此Linux多線程的學習重點是進程間通訊的方式。
【多線程和多線程的區別:】
- 多進程數據是分開的,共享複雜,需要用 IPC,同步簡單;多線程共享進程數據:共享簡單,同步複雜。
- 多進程創建銷燬、切換複雜,速度慢 ;多線程創建銷燬、切換簡單,速度快。
- 多進程佔用內存多, CPU 利用率低;多線程佔用內存少, CPU 利用率高。
- 多進程編程簡單,調試簡單;多線程編程複雜,調試複雜。
- 多進程間不會相互影響 ;多線程一個線程掛掉將導致整個進程掛掉。
- 多進程適應於多核、多機分佈;多線程適用於多核
我們總結爲一張表:
多進程 | 多線程 | 總結 | |
---|---|---|---|
數據共享、同步 | 數據共享複雜,需要用IPC;數據是分開的,同步簡單 | 數據共享簡單,用全局變量即可,同步複雜 | 根據情況選擇 |
內存、CPU | 佔用內存多,切換複雜,CPC利用率低 | 佔用內存少,切換簡單,CPU利用率高 | 線程較好 |
創建銷燬,切換 | 創建銷燬,切換複雜,速度慢 | 創建銷燬 | 切換簡單 |
編程 | 編程簡單,調試簡單 | 編程複雜,調試複雜 | 進程較好 |
可靠性 | 進程間不會相互影響 | 一個線程出現問題對整個進程有影響 | 進程較好 |
分佈式 | 適用於多核,多機分佈式;如果一臺機器不夠,擴展到多臺機器比較簡單 | 適應於多核分佈式 | 進程較好 |
(七)協程
- 協程是用戶模式下的輕量級線程,操作系統內核對協程一無所知。
- 協程的調度完全由應用程序來控制,操作系統不管這部分的調度。
- 一個線程可以包含一個或多個協程。
- 協程擁有自己的寄存器上下問和棧,協程調度切換時,將寄存器上下文和棧保存起來,在切換回來時先恢復保存的上下文和棧。
- 協程能保留上一次調用時的狀態。
- Windows下協程又稱爲纖程。
- 應用:python,Java,Go等語言。
(八)線程&&協程
區別:
- 線程是由CPU調度,而協程是由用戶調度。
- 線程存在安全問題,協程比線程較安全。
- 線程使用同步機制,協程使用異步機制。
- 協程完全由程序控制,而線程的阻塞狀態由操作系統內核控制切換,所以協程性能提升。
- 協程的開銷遠遠小於線程。
用一張圖來標識進程,線程,協程之間的關係:
(九)管程
管程:由存儲共享資源的數據結構,對該共享數據結構實施操作的一組過程所組成的資源管理程序共同構成的一個操作系統的資源管理模塊。
簡單來說管程就是用來管理進程的。所謂的管程實際上是定義的一種數據結構和控制進程的一些操作的集合。
二、鎖的實現
爲了保證臨界區代碼的安全,操作系統引入了鎖機制。通過鎖機制,能夠保證在多核多線程環境中,在某一個時間點上,只能有一個線程進入臨界區代碼,從而保證臨界區中操作數據的一致性。
因爲鎖機制的一個特點是他的同步原語都是原子操作硬件已經爲我們提供了一些原子操作,如下:
- 忙等待:中斷禁止和啓用
- 內存加載和存入
- 非忙等待:測試與設置指令
他們都是硬件步驟,中間沒有辦法插入別的操作。在這些硬件原子操作之上,我們便可以構建軟件原子操作——鎖、睡眠與叫醒、信號量等。
三、鎖的類型
在使用鎖時需要明確幾個問題:
- 鎖的所有權問題,一般誰加鎖誰就負責解鎖。
- 鎖的作用就是對臨界區資源的讀寫操作的安全限制
- 鎖是否可以被多個使用者使用(互不影響的使用者對資源的佔用)
- 多個臨界區資源鎖的循環問題(死鎖場景)
(一)互斥鎖mutex
互斥鎖是線程同步中常用的一種鎖,一般操作步驟爲:
- 使用互斥鎖時在訪問共享資源之前對進行加鎖操作,在訪問完成之後進行解鎖操作,誰加鎖誰釋放;
- 加鎖後,任何其他試圖再次加鎖的線程會被阻塞,直到當前進程解鎖。
互斥鎖無法獲取鎖時將阻塞睡眠,需要系統來喚醒,解鎖時有多個線程阻塞,那麼所有該鎖上的線程都變成就緒狀態, 第一個變爲就緒狀態的線程又執行加鎖操作,那麼其他的線程又會進入等待,對其他線程而言就是虛假喚醒。 在這種方式下,只有一個線程能夠訪問被互斥鎖保護的資源。
(二)讀寫鎖rwlock
讀寫鎖也叫共享-互斥鎖:讀模式共享和寫模式互斥。
本質上這種非常合理,因爲在數據沒有被寫的前提下,多個使用者讀取時完全不需要加鎖的。讀寫鎖有讀加鎖狀態、寫加鎖狀態和不加鎖狀態三種狀態,當讀寫鎖在寫加鎖模式下,任何試圖對這個鎖進行加鎖的線程都會被阻塞,直到寫進程對其解鎖。
讀寫鎖寫優先。讀寫鎖適合對數據結構讀的次數遠大於寫的情況下使用
(三)自旋鎖spinlock
自旋鎖的主要特徵是線程在想要獲得臨界區執行權限時,如果臨界區已經被加鎖,那麼自旋鎖並不會阻塞睡眠,等待系統來主動喚醒,而是原地忙輪詢資源是否被釋放加鎖,自旋就是自我旋轉。
自旋鎖優點:
- 避免了系統的喚醒,自己來執行輪詢,即輪詢忙等待。
- 如果在臨界區的資源代碼非常短且是原子的,那麼使用起來是非常方便的,避免了各種上下文切換,開銷非常小,因此在內核的一些數據結構中自旋鎖被廣泛的使用。
自旋鎖缺點:
- 在單核cpu下不起作用:被自旋鎖保護的臨界區代碼執行時不能進行掛起狀態。會造成死鎖。
- 自旋鎖的初衷是在短期間內進行輕量級的鎖定。一個被爭用的自旋鎖使得請求它的線程在等待鎖重新可用的期間進行自旋(特別浪費處理器時間),所以自旋鎖不應該被持有時間過長。如果需要長時間鎖定的話, 最好使用信號量。
其實,自旋鎖與互斥鎖比較類似,它們都是爲了解決對某項資源的互斥使用。無論是互斥鎖,還是自旋鎖,在任何時刻,最多隻能有一個保持者,也就說,在任何時刻最多隻能有一個線程獲得鎖。但是兩者在調度機制上略有不同。對於互斥鎖,如果資源已經被佔用,資源申請者只能進入睡眠狀態。但是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環在那裏看是否該自旋鎖的保持者已經釋放了鎖,"自旋"一詞就是因此而得名。
四、死鎖
多個進程爭奪資源過程中形成的一種僵局狀態。當進程處於這種僵持狀態時,若無外力作用,它們都將無法再向前推進。兩個及以上個進程都在等待對方執行完畢才能繼續往下執行的時候就發生了死鎖,這些線程都陷入了無限的等待中。
(一)死鎖產生的原因
產生死鎖的原因可歸結爲:競爭資源;進程間推進順序非法
- 競爭資源
- 競爭不可剝奪資源(例如:系統中只有一臺打印機,可供進程P1使用,假定P1已佔用了打印機,若P2繼續要求打印機打印將阻塞)
- 競爭臨時資源(臨時資源包括硬件中斷、信號、消息、緩衝區內的消息等),通常消息通信順序進行不當,則會產生死鎖。
- 進程間推進順序非法
兩個線程都各自保持了資源,系統處在了不安全的狀態,因爲兩個線程向前推進,便可能發生死鎖。
(二)死鎖產生的必要條件
- 互斥條件: 同一時間只能一個進程使用這個資源。
- 請求與保持請求: 一個進程因請求資源而阻塞時,對已獲資源保持不變。
- 不剝奪條件: 不能強制剝奪進程的資源。
- 循環等待: 若干進程形成頭尾相連的循環等待資源關係。
(三)處理死鎖的方法
1. 預防:
- 破壞請求條件: 資源一次性分配,一次性分配所有資源,這樣就不會再有請求了。
- 破壞保持條件: 只要有一個資源得不到分配,也不給這個進程分配其他的資源。
- 破壞不可剝奪條件: 可剝奪資源,即當某進程獲得了部分資源,但得不到其它資源,則釋放已佔有的資源。
- 破壞循環等待:用資源有序分配算法,系統給每類資源賦予一個編號,每一個進程按編號遞增的順序請求資源,釋放則相反,這樣可以及時檢測死鎖發生,爲解除死鎖創造條件。
2. 避免:
預防死鎖的幾種策略,會嚴重地損害系統性能。因此在避免死鎖時,要施加較弱的限制,從而獲得較滿意的系統性能。
由於在避免死鎖的策略中,允許進程動態地申請資源。因而,系統在進行資源分配之前預先計算資源分配的安全性。若此次分配不會導致系統進入不安全的狀態,則將資源分配給進程;否則,進程等待。其中最具有代表性的避免死鎖算法是銀行家算法。
銀行家算法步驟:
- 首先需要定義狀態和安全狀態的概念。
- 系統的狀態是當前給進程分配的資源情況。
- 因此,狀態包含兩個向量Resource(系統中每種資源的總量)和Available(未分配給進程的每種資源的總量)及兩個矩陣Claim(表示進程對資源的需求)和Allocation(表示當前分配給進程的資源)。
- 安全狀態是指至少有一個資源分配序列不會導致死鎖。
- 當進程請求一組資源時,假設同意該請求,從而改變了系統的狀態,然後確定其結果是否還處於安全狀態。如果是,同意這個請求;如果不是,阻塞該進程知道同意該請求後系統狀態仍然是安全的。
3. 檢測:
此時資源已經分配,所以我們可以爲每個進程和每個資源指定一個唯一的號碼,建立資源分配表和進程等待表,用資源分配圖化簡法來檢測死鎖。
4. 解除:
當發現有進程死鎖後,應立即把它從死鎖狀態中解脫出來,常採用的方法有:
- 剝奪資源:從其它進程剝奪足夠數量的資源給死鎖進程,以解除死鎖狀態;
- 撤消進程:可以直接撤消死鎖進程或撤消代價最小的進程,直至有足夠的資源可用,死鎖狀態.消除爲止;所謂代價是指優先級、運行代價、進程的重要性和價值等。
(四)四個算法
- 銀行家算法:避免死鎖(資源動態分配的過程中,用某種方法去阻止它進入不安全狀態);不那麼嚴格的限制產生死鎖的必要條件的存在,而是在系統運行過程中小心的避免死鎖的最終發生。每次先計算此次分配資源的安全性,若分配不會導致系統進入不安全狀態,則分配,否則等待。
- 資源有序分配法 :預防死鎖(破壞死鎖四個必要條件之一);系統中所有的資源都按某種規則統一編號,所有分配請求必須使用上升的次序進行,當遵守上升次序的規則時,若資源可以,則予以分配,否則等待。這樣破壞了循環等待資源條件。
- 資源分配圖化簡法:檢測死鎖(及時檢測死鎖發生,爲解除死鎖創造條件),如果經過化簡後,節點都不能化簡爲孤立節點,則代表形成死鎖。
- 撤銷進程法:解除死鎖(常用方法是撤銷或掛起一些進程,回收資源,分配給已經阻塞的,讓它運行)。