深刻理解併發/並行,阻塞/非阻塞,同步/異步

1. 阻塞,非阻塞

 

首先,阻塞這個詞來自操做系統的線程/進程的狀態模型中,以下圖:算法

一個線程/進程經歷的5個狀態,建立,就緒,運行,阻塞,終止。各個狀態的轉換條件如上圖,其中有個阻塞狀態,就是說當線程中調用某個函數,須要IO請求,或者暫時得不到競爭資源的,操做系統會把該線程阻塞起來,避免浪費CPU資源,等到獲得了資源,再變成就緒狀態,等待CPU調度運行。併發

阻塞調用是指調用結果返回以前,調用者會進入阻塞狀態等待。只有在獲得結果以後纔會返回。異步

非阻塞調用是指在不能馬上獲得結果以前,該函數不會阻塞當前線程,而會馬上返回。socket

阻塞調用:好比 socket 的 recv(),調用這個函數的線程若是沒有數據返回,它會一直阻塞着,也就是 recv() 後面的代碼都不會執行了,程序就停在 recv() 這裏等待,因此通常把 recv() 放在單獨的線程裏調用。函數

非阻塞調用:好比非阻塞socket 的 send(),調用這個函數,它只是把待發送的數據複製到TCP輸出緩衝區中,就馬上返回了,線程並不會阻塞,數據有沒有發出去 send() 是不知道的,不會等待它發出去才返回的。oop

拓展spa

若是線程始終阻塞着,永遠得不到資源,因而就發生了死鎖。操作系統

好比A線程要X,Y資源才能繼續運行,B線程也要X,Y資源才能運行,但X,Y同時只能給一個線程用(即互斥條件)用的時候其餘線程又不能搶奪。線程

A 有 X,等待 Y。 
B 有 Y,等待 X。
code

因而A,B發生了循環等待,形成死鎖。給用戶的感受就是程序卡着不動了。

在寫代碼的時候要特別注意共享資源的使用,用信號量控制好,避免形成死鎖。死鎖的解除有個著名的銀行家算法

阻塞和掛起:阻塞是被動的,好比搶不到資源。掛起是主動的,線程本身調用 suspend() 把本身退出運行態了,某些時候調用 resume() 又恢復運行。

線程執行完就會被銷燬,若是不想線程被頻繁的建立,銷燬,怎麼辦?能夠給線程裏面寫個死循環,或者讓線程有任務的時候執行,沒任務的時候掛起,就像iOS中的 runloop 機制同樣。線程就不會隨便的終止了。

 

2. 同步,異步

 

同步:在發出一個同步調用時,在沒有獲得結果以前,該調用就不返回。

異步:在發出一個異步調用後,調用者不會馬上獲得結果,該調用就返回了。

同步例子

int n = func();
next();
// func() 的結果沒有返回,next() 就不會執行,直到 func() 運行完。

 

異步例子

func(callback);
next();
...

void callback(int n)     // func 結果回調
{
  int k = n;
}
// func() 執行後,還沒得出結果就當即返回,而後執行 next() 了
// 等到結果出來,func() 回調 callback() 通知調用者結果。

 

同步的定義看起來跟阻塞很像,可是同步跟阻塞是兩個概念,同步調用的時候,線程不必定阻塞,調用雖然沒返回,但它仍是在運行狀態中的,CPU極可能還在執行這段代碼,而阻塞的話,它就確定不在CPU中跑這個代碼了。這就是同步和阻塞的區別。同步是確定能夠在,阻塞是確定不在。

異步和非阻塞的定義比較像,二者的區別是異步是說調用的時候結果不會立刻返回,線程可能被阻塞起來,也可能不阻塞,二者不要緊。非阻塞是說調用的時候,線程確定不會進入阻塞狀態。

上面兩組概念,就有4種組合。

同步阻塞調用:得不到結果不返回,線程進入阻塞態等待。

同步非阻塞調用:得不到結果不返回,線程不阻塞一直在CPU運行。

異步阻塞調用:去到別的線程,讓別的線程阻塞起來等待結果,本身不阻塞。

異步非阻塞調用:去到別的線程,別的線程一直在運行,直到得出結果。

 

3. 併發,並行

 

先從定義提及,定義通過我通俗化了,原定義有點難理解。

併發是指一個時間段內,有幾個程序都在同一個CPU上運行,但任意一個時刻點上只有一個程序在處理機上運行。

並行是指一個時間段內,有幾個程序都在幾個CPU上運行,任意一個時刻點上,有多個程序在同時運行,而且多道程序之間互不干擾。 二者區別以下圖

 

並行是多個程序在多個CPU上同時運行,任意一個時刻能夠有不少個程序同時運行,互不干擾。

併發是多個程序在一個CPU上運行,CPU在多個程序之間快速切換,微觀上不是同時運行,任意一個時刻只有一個程序在運行,但宏觀上看起來就像多個程序同時運行同樣,由於CPU切換速度很是快,時間片是64ms(每64ms切換一次,不一樣的操做系統有不一樣的時間),人類的反應速度是100ms,你還沒反應過來,CPU已經切換了好幾個程序了。

舉個例子吧,並行就是,多我的,有人在掃地,有人在作飯,有人在洗衣服,掃地,作飯,洗衣服都是同時進行的。 
併發就是,有一我的,這我的一下子掃地,一下子作飯,一下子洗衣服,他在這3件事中來回作,同一時刻只作一件事,不是同時作的,但最後3件事均可以作完。

時間片大小的選取 
時間片取的小,假設是20ms,切換耗時假設是 10ms。 
那麼用戶感受不到多個程序之間會卡,響應很快,由於切換太快了,可是CPU的利用率就低了,20 / (20 + 10) = 66% 只有這麼多,33%都浪費了。

時間片取的大,假設是200ms,切換耗時是 10ms 
那麼用戶會以爲程序卡,響應慢,由於要200ms後才輪到個人程序運行,可是CPU利用率就高了,200 / (200 + 10) = 95% 有這麼多被利用的。

因此時間片取太大或者過小都很差,通常在 10 - 100 ms 之間。

CPU調度策略

在併發運行中,CPU須要在多個程序之間來回切換,那麼如何切換就有一些策略

3.1 先來先服務 - 時間片輪轉調度

這個很簡單,就是誰先來,就給誰分配時間片運行,缺點是有些緊急的任務要好久才能獲得運行。

3.2 優先級調度

每一個線程有一個優先級,CPU每次去拿優先級高的運行,優先級低的等等,爲了不等過久,每等必定時間,就給線程提升一個優先級

3.3 最短做業優先

把線程任務量排序,每次拿處理時間短的線程運行,就像我去銀行辦業務同樣,個人事情很快就處理完了,因此讓我插隊先辦完,後面時間長的人先等等,時間長的人就很可貴到響應了。

3.4 最高響應比優先

用線程的等待時間除以服務時間,獲得響應比,響應比小的優先運行。這樣不會形成某些任務一直得不到響應。

3.5 多級反饋隊列調度

有多個優先級不一樣的隊列,每一個隊列裏面有多個等待線程。  CPU每次從優先級高的遍歷到低的,取隊首的線程運行,運行完了放回隊尾,優先級越高,時間片越短,即響應越快,時間片就不是固定的了。  隊列內部仍是用先來先服務的策略。

相關文章
相關標籤/搜索