計算機發展起初,CPU 資源十分昂貴,若是讓 CPU 只能運行一個程序那麼當 CPU 空閒下來(例如等待 I/O 時),CPU 資源就會被浪費,爲了使 CPU 資源獲得更好的利用,先驅編寫了一個監控程序,若是發現某個程序暫時無需使用 CPU 時,監控程序就把另外的正在等待 CPU 資源的程序啓動起來,以充分利用 CPU資源。這種方法稱爲 - 多道程序(Multiprogramming)程序員
對於多道程序,最大的弊端是各程序之間不區分輕重緩急,對於用戶交互式的程序來講,對 CPU 計算時間的需求並很少,可是對於響應速度卻有比較高的要求。而對於計算類程序來講則相反,對響應速度要求低,但須要長時間的 CPU 計算。想象一個場景:我在同時在瀏覽網頁和聽音樂,咱們但願瀏覽器可以快速響應,同時也但願音樂不停,這時候多道程序就無法達到咱們的要求了。算法
因而人們改進了多道程序,使得每一個程序運行一段時間以後,都主動讓出 CPU 資源,這樣每一個程序在一段時間內都有機會運行一小段時間。這樣像瀏覽器這樣的交互式程序就可以快速地被處理,同時計算類程序也不會受到很大影響。這種程序協做方式被稱爲 分時系統(Time-Sharing System)。瀏覽器
在分時系統的幫助下,咱們能夠邊用瀏覽器邊聽歌了。可是若是某個程序出現了錯誤,致使了死循環,不只僅是這個程序會出錯,整個系統都會死機,爲了不這種狀況,一個更爲先進的操做系統模式被髮明處理,也就是咱們如今熟悉的多任務系統(Multi-tasking System)。服務器
操做系統從最底層接管了全部硬件資源。全部的應用程序在操做系統上以 進程(Process) 的方式運行,每一個進程都有本身獨立的地址空間,相互隔離。CPU 由操做系通通一統一進行分配。每一個進程都有機會獲得 CPU,同時在操做系統控制下,若是一個進程運行超過了必定時間,就會被暫停掉,失去 CPU 資源。這樣就避免了一個程序的錯誤致使整個系統死機。若是操做系統分配給各個進程的運行時間都很短,CPU 能夠在多個進程間快速切斷,就像不少進程都同時在運行的樣子。幾乎全部現代操做系統都是採用這樣的方式支持多任務。微信
進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎。它能夠申請和擁有系統資源,是一個活動的實體。它不僅是程序的代碼,還包括當前的活動,經過程序計數器的值和處理遞存器的內容來表示。網絡
幾種狀態的切換:多線程
高級、中級和低級調度做業從提交開始直到完成,每每要經歷下述三級調度:併發
CPU 任務能夠分爲交互式任務和批處理任務,調度最終的目標是合理的使用 CPU,使得交互式任務的響應時間儘量短,用戶不至於感到延遲,同時使得批處理任務的週轉時間儘量短,減小用戶等待的時間。函數
FIFO 或 First Come,First Served(FCFS)
調度的順序就是任務到達就緒隊列的順序。
公平、簡單(FIFO 隊列)、非搶佔、不適合交互式。未考慮任務特性,平均等待時間能夠縮短。性能
Shortest Job First(SJF)
最短的做業(CPU 區間長度最小)優先調度
能夠證實,SJF 能夠保證最小的平均等待時間
Shortest Job First (SRJF): SJF 的可搶佔版本,比 SJF 更有優點
SJF、SRJF 如何知道下一 CPU 區間大小?根據歷史進行預測:指數平均法。
優先權調度
每一個任務關聯一個優先權、調度優先權最高的任務。
注意:優先權過低的任務一直就緒,得不到運行,出現「飢餓」現象。
FCFS 是 RR 的特例,SJF 是優先權調度的特例,這些調度算法都不適合於交互式系統。
Round-Robin(RR)
設置一個時間片,按時間片來輪轉調度(「輪叫」算法)
優勢:定時有響應,等待時間較短;缺點:上下文切換次數較多;
如何肯定時間片?時間片太大,響應時間太長;吞吐量變小,週轉時間變長;當時間片過長時,退化爲 FCFS。
多級隊列調度
按照必定的規則創建多個進程隊列
不一樣的隊列有固定的優先級(高優先級有搶佔權)
不一樣的隊列能夠給不一樣的時間片和採用不一樣的調度方法
存在問題 1:無法區分 I/O bound 和 CPU bound;
存在問題 2:也存在必定程度的「飢餓」現象
多級反饋隊列
在多級隊列的基礎上,任務能夠在隊列之間移動,更細緻的區分任務。
能夠根據「享用」CPU 時間多少來移動隊列,阻止「飢餓」。
最通用的調度算法,多數 OS 都使用該方法或其變形,如 UNIX、Windows 等。
在操做系統中,進程是佔有資源的最小單位(線程能夠訪問其所在進程內的全部資源,但線程自己並不佔有資源或僅僅佔有一點必須資源)。但對於某些資源來講,其在同一時間只能被一個進程所佔用。這些一次只能被一個進程所佔用的資源就是所謂的臨界資源。
典型的臨界資源好比物理上的打印機,或是存在硬盤或內存中被多個進程所共享的一些變量和數據等(若是這類資源不被當作臨界資源加以保護,那麼頗有可能形成丟數據的問題)。
對於臨界資源的訪問,必須是互斥進行。也就是當臨界資源被佔用時,另外一個申請臨界資源的進程會被阻塞,直到其所申請的臨界資源被釋放。而進程內訪問臨界資源的代碼被稱爲臨界區。
解決臨界區問題可能的方法:
信號量是一個肯定的二元組(s,q),其中 s 是一個具備非負初值的整型變量,q 是一個初始狀態爲空的隊列,整型變量 s 表示系統中某類資源的數目:
除信號量的初值外,信號量的值僅能由 P 操做和 V 操做更改,操做系統利用它的狀態對進程和資源進行管理。
P 操做:P 操做記爲 P(s),其中 s 爲一信號量,它執行時主要完成如下動做:
// 可理解爲佔用一個資源,若原來就沒有則記帳「欠」1 個 s.value = s.value - 1;
若 s.value ≥ 0
,則進程繼續執行,不然(即s.value < 0
),則進程被阻塞,並將該進程插入到信號量 s 的等待隊列 s.queue 中。
實際上,P 操做能夠理解爲分配資源的計數器,或是使進程處於等待狀態的控制指令
V 操做:V 操做記爲 V(s),其中 s 爲一信號量,它執行時,主要完成如下動做:
// 可理解爲歸還一個資源,若原來就沒有則意義是用此資源還 1 個欠帳 s.value = s.value + 1;
若 s.value > 0
,則進程繼續執行,不然(即 s.value ≤ 0
),則從信號量 s 的等待隊列 s.queue 中移出第一個進程,使其變爲就緒狀態,而後返回原進程繼續執行。
實際上,V 操做能夠理解爲歸還資源的計數器,或是喚醒進程使其處於就緒狀態的控制指令
信號量方法實現:生產者 - 消費者互斥與同步控制
semaphore fullBuffers = 0;//倉庫中已填滿的貨架個數 semaphore emptyBuffers = BUFFER_SIZE;//倉庫貨架空閒個數 semaphore mutex = 1;//生產 - 消費互斥信號 Producer() { while(True) { /*生產產品item*/ emptyBuffers.P(); mutex.P(); /*item存入倉庫buffer*/ mutex.V(); fullBuffers.V(); } } Consumer() { while(True) { fullBuffers.P(); mutex.P(); /*從倉庫buffer中取產品item*/ mutex.V(); emptyBuffers.V(); /*消費產品item*/ } }
死鎖:多個進程因循環等待而形成的沒法執行的現象
死鎖會形成進程沒法執行,同時會形成系統資源的極大浪費(資源沒法釋放)。
死鎖產生的 4 個必要條件:
死鎖的避免:銀行家算法
思想:判斷這次請求是否形成死鎖,若會形成死鎖,則拒絕該請求。
本地進程間通訊的方式有不少,能夠總結爲下面四類:
多線程解決了前面提到的多任務問題。然而不少時候不一樣的程序須要共享一樣的資源(文件,信號量等),若是全都使用進程的話會致使切換的成本很高,形成 CPU 資源的浪費。因而出現了線程的概念。
線程,有時被稱爲輕量級進程(Lightweight Process,LWP),是程序執行流的最小單元。一個標準的線程由線程 ID,當前指令指針(PC),寄存器集合和堆棧組成。
線程具備如下屬性:
輕型實體
線程中的實體基本上不擁有系統資源,只是有一點必不可少的、能保證獨立運行的資源。線程的實體包括:程序、數據和 TCB(Thread Control Block)。線程是動態概念,它的動態特性由線程控制塊 TCB 描述。
獨立調度和分派的基本單位
在多線程 OS 中,線程是能獨立運行的基本單位,於是也是獨立調度和分派的基本的單位。因爲線程很「輕」,故線程的切換很是迅速且開銷小(在同一進程中的)
可併發執行
在一個進程中的多個線程之間,能夠併發執行,甚至容許在一個進程中全部線程都能併發執行;
共享進程資源
在同一進程中的各個線程,均可以共享該進程所擁有的資源,這首先表如今:全部線程都具備相同的地址空間(進程的地址空間),這意味着,線程能夠訪問改地址空間的每個虛擬地址;此外,還能夠訪問進程所擁有的已打開文件、定時器、信號量等。因爲同一個進程內的線程共享內存和文件,因此線程之間互相通訊沒必要調用內核。
線程共享的環境包括:進程代碼段、進程的公有數據(利用這些共享的數據,線程很容易的實現相互以前的通信)、進程打開的文件描述符、信號的處理器、進程的當前目錄和進程用戶 ID 與進程組 ID。
鎖要解決的是線程之間爭奪資源的問題:
上面幾個角度並不是相互獨立,在實際場景中須要將他們集合起來才能構造出一個合適的鎖。
當一個共享資源只有一份的時候,一般咱們使用獨佔鎖,常見的即各個語言中的 Mutex
。當共享資源有多份時,可使用信號量(Semaphere)。
對於互斥鎖來講,若是一個線程已經鎖定了一個互斥鎖,第二個線程又試圖去獲取這個互斥鎖,則第二個線程將會被掛起(即休眠、不佔用 CPU 資源)。
在計算機系統中,頻繁的掛起和切換線程,也是有成本的。自旋鎖就是解決這個問題的。
自旋鎖:指當一個線程在獲取鎖的時候,若是鎖已經被其餘線程獲取,那麼該線程將循環等待,而後不斷的判斷鎖是否可以被成功獲取,直到獲取到鎖纔會退出循環。
容易看出,當資源等待的時間較長,用互斥鎖讓線程休眠,會消耗更少的資源,當資源等待的時間較短時,使用自旋鎖將減小線程的切換,得到更高的性能。
Java 中的 synchornized
和 .NET 中的 lock
(Monitor
)的實現,是結合了兩種鎖的特色。簡單說,它們在發現資源被搶佔以後,會先試着自旋等待一段時間,若是等待時間太長,則會進入掛起狀態。經過這樣的實現,能夠較大程度上挖掘出鎖的性能。
可重入鎖(ReetrantLock),也叫做遞歸鎖,指的是同一線程內,外層函數得到鎖以後,內層遞歸函數仍然能夠獲取到該鎖。
換而言之:同一線程再次進入同步代碼時,可使用本身已獲取到的鎖。
使用可重入鎖時,在同一線程中屢次獲取鎖,不會致使死鎖。使用不可重入鎖,則會致使死鎖發生。
Java 中的 synchornized
和 .NET 中的 lock
(Monitor
) 都是可重入的。
有些狀況下,對於共享資源讀競爭的狀況遠遠多於寫競爭,這種狀況下,對讀操做每次都進行加鎖,是得不償失的。讀寫鎖就是爲了解決這個問題。
讀寫鎖容許同一時刻被多個讀線程訪問,可是在寫線程訪問時,全部讀線程和其餘的寫線程都會被阻塞。簡單能夠總結爲,讀讀不互斥,讀寫互斥,謝謝互斥。
對讀寫鎖來講,有一個升級和降級的概念,即當前得到了讀鎖,想把當前的鎖變成寫鎖,成爲升級,反之稱爲降級。鎖的升降級自己也是爲了提高性能,經過改變當前鎖的性質,避免重複獲取鎖。
協程,又稱爲微線程,纖程。英文名: Coroutine
協程能夠理解爲用戶級線程,協程和線程的區別是:線程是搶佔式的調度,而協程是協同式的調度,協程避免了無心義的調度,由此能夠提升性能,但也所以,程序員必須本身承擔調度的責任,同時,協程也失去了標準線程使用多 CPU 的能力。
IO 多路複用是指內核一旦發現進程指定的一個或者多個 IO 條件準備讀取,他就通知該進程。IO 多路複用適用於以下場景:
與多進程和多線程技術相比,I/O 多路複用技術的最大優點是系統開銷小,系統沒必要建立 進程/線程,也沒必要維護這些 進程/線程,從而大大減少了系統的開銷。
一個沒有雞湯只有乾貨的公衆號 ****************************************************