我已經理解了併發和並行的區別

理解併發、並行的例子

先舉例子來理解這2個概念的區別。程序員

老師讓兩個同窗去辦公室談話。若是這兩同窗(進程)是並列跨過辦公室門(CPU)的,那麼就是並行。若是同窗A先進同窗B後進入(或者先B後A),或者兩人並列同時進入,可是在辦公室外的路人甲(用戶)看來,同窗A和同窗B同時都在辦公室內,這是併發。web

其實這個例子不合理,由於真正的並行是多核CPU下的概念,但上面這個簡單的例子很是有助於理解。算法

若是舉例要精確一點,那麼大概是這樣的:進辦公室有兩個門(兩CPU),若是兩同窗分別從不一樣的門進入,無論前後性,二者互相獨立,那麼是並行;若是兩同窗無論以什麼方式進入,在路人甲看來,他兩同時都在辦公室內,就是併發。服務器

我不信到如今還不理解併發和並行。數據結構

併發和並行的理論性解釋

爲何操做系統上能夠同時運行多個程序而用戶感受不出來?多線程

這是由於不管是單CPU仍是多CPU,操做系統都營造出了能夠同時運行多個程序的假象。實際的過程操做系統對進程的調度以及CPU的快速上下文切換實現的:每一個進程執行一會就先停下來,而後CPU切換到下個被操做系統調度到的進程上使之運行。由於切換的很快,使得用戶認爲操做系統一直在服務本身的程序。併發

再來解釋併發就容易理解多了。異步

併發(concurrent)指的是多個程序能夠同時運行的現象,更細化的是多進程能夠同時運行或者多指令能夠同時運行。但這不是重點,在描述併發的時候也不會去扣這種字眼是否精確,併發的重點在於它是一種現象。併發描述的是多進程同時運行的現象。但實際上,對於單核心CPU來講,同一時刻只能運行一個進程。因此,這裏的"同時運行"表示的不是真的同一時刻有多個進程運行的現象,這是並行的概念,而是提供一種功能讓用戶看來多個程序同時運行起來了,但實際上這些程序中的進程不是一直霸佔CPU的,而是執行一會停一會。函數

因此,併發和並行的區別就很明顯了。它們雖然都說是"多個進程同時運行",可是它們的"同時"不是一個概念。並行的"同時"是同一時刻能夠多個進程在運行(處於running),併發的"同時"是通過上下文快速切換,使得看上去多個進程同時都在運行的現象,是一種OS欺騙用戶的現象操作系統

實際上,當程序中寫下多進程或多線程代碼時,這意味着的是併發而不是並行。併發是由於多進程/多線程都是須要去完成的任務,不併行是由於並行與否由操做系統的調度器決定,可能會讓多個進程/線程被調度到同一個CPU核心上。只不過調度算法會盡可能讓不一樣進程/線程使用不一樣的CPU核心,因此在實際使用中幾乎老是會並行,但卻不能以100%的角度去保證會並行。也就是說,並行與否程序員沒法控制,只能讓操做系統決定

再次註明,併發是一種現象,之因此能有這種現象的存在,和CPU的多少無關,而是和進程調度以及上下文切換有關的。

理解了概念,再來深刻擴展下。

串行、並行和併發

任務描述

如圖:

1559104246414

任務是將左邊的一堆柴所有搬到右邊燒掉,每一個任務包括三個過程:取柴,運柴,放柴燒火。

這三個過程分別對應一個函數:

func get { geting }
func carry { carrying }
func unload { unloading }

串行模式

串行表示全部任務都一一按前後順序進行。串行意味着必須先裝完一車柴才能運送這車柴,只有運送到了,才能卸下這車柴,而且只有完成了這整個三個步驟,才能進行下一個步驟

和稍後所解釋的並行相對比,串行是一次只能取得一個任務,並執行這個任務

假設這堆柴須要運送4次才能運完,那麼當寫下的代碼相似於下面這種時,那麼就是串行非併發的模式:

for(i=0;i<4;i++){
    get()
    carry()
    unload()
}

或者,將三個過程的代碼所有集中到一個函數中也是如此:

func task {
    geting
    carrying
    unloading
}

for(i=0;i<4;i++){
    task()
}

這兩種都是串行的代碼模式。畫圖描述:

1559105891580

並行模式

並行意味着能夠同時取得多個任務,並同時去執行所取得的這些任務。並行模式至關於將長長的一條隊列,劃分紅了多條短隊列,因此並行縮短了任務隊列的長度

正如前面所舉的兩同窗進辦公室的例子,串行的方式下,必須1個同窗進入後第二個同窗才進入,隊列長度爲2,而並行方式下能夠同時進入,隊列長度減半了。

並行的效率從代碼層次上強依賴於多進程/多線程代碼,從硬件角度上則依賴於多核CPU

對於單進程/單線程,因爲只有一個進程/線程在執行,因此儘管同時執行所取得的多個任務,但實際上這個進程/線程是不斷的在多任務之間切換,一會執行一下這個,一會執行一下那個,就像是一我的在不一樣地方之間來回奔波。因此,單進程/線程的並行,效率比串行更低。

對於多進程/多線程,各進程/線程均可以執行各自所取得的任務,這是真正的並行。

可是,還須要考慮硬件層次上CPU核心數,若是隻有單核CPU,那麼在硬件角度上這單核CPU一次也只能執行一個任務,上面多進程/多線程的並行也並不是真正意義上的並行。只有多核CPU,而且多進程/多線程並行,纔是真正意義上的並行。

以下圖,是多進程/多線程(2個工做者)的並行:

1559106284586

併發

並發表示多個任務同時都要執行的現象,更詳細的概念前面已經說面的夠具體了。

其實,不少場景下都會使用併發的概念。好比同時500個http請求涌向了web服務器,好比有時候說併發數是1000等。

有時候也將併發當成任務,好比500併發數意味着500個任務,表示的是在一個特定的時間段內(約定俗成的你們認爲是1秒)能夠完成500個任務。這500個任務能夠是單進程/單線程方式處理的,這時表示的是併發不併行的模式(coroutine就是典型的併發不併行),即先執行完一個任務後才執行另外一個任務,也能夠是多進程/多線程方式處理的,這時表示的是併發且並行模式。

要解決大併發問題,一般是將大任務分解成多個小任務。很典型的一個例子是處理客戶端的請求任務,這個大任務裏面包含了監聽並創建客戶端的鏈接、處理客戶端的請求、響應客戶端。但基本上全部這類程序,都將這3部分任務分開了:在執行任何一個小任務的時候,均可以經過一些手段使得能夠執行其它小任務,好比在處理請求的時候,能夠繼續保持監聽狀態。

因爲操做系統對進程的調度是隨機的,因此切分紅多個小任務後,可能會從任一小任務處執行。這可能會出現一些現象:

  • 可能出現一個小任務執行了屢次,還沒開始下個任務的狀況。這時通常會採用隊列或相似的數據結構來存放各個小任務的成果。好比負責監聽的進程已經執行了屢次,創建了多個鏈接,可是尚未調度處處理請求的進程去處理任何一個請求。

  • 可能出現還沒準備好第一步就執行第二步的可能。這時,通常採用多路複用或異步的方式,好比只有準備好產生了事件通知才執行某個任務。好比尚未和任何一個客戶端創建鏈接時,就去執行了處理請求的進程。
  • 能夠多進程/多線程的方式並行執行這些小任務。也能夠單進程/單線程執行這些小任務,這時極可能要配合多路複用才能達到較高的效率

看圖很是容易理解:

1559106934889

上圖中將一個任務中的三個步驟取柴、運柴、卸柴劃分紅了獨立的小任務,有取柴的老鼠,有運柴的老鼠,有卸柴燒火的老鼠。

若是上圖中全部的老鼠都是同一只,那麼是串行併發的,若是是不一樣的多隻老鼠,那麼是並行併發的

總結

並行和串行:

  • 串行:一次只能取得一個任務並執行這一個任務
  • 並行:能夠同時經過多進程/多線程的方式取得多個任務,並以多進程或多線程的方式同時執行這些任務
  • 注意點:
    • 若是是單進程/單線程的並行,那麼效率比串行更差
    • 若是隻有單核cpu,多進程並行並無提升效率
    • 從任務隊列上看,因爲同時從隊列中取得多個任務並執行,至關於將一個長任務隊列變成了短隊列

併發:

  • 併發是一種現象:同時運行多個程序或多個任務須要被處理的現象
  • 這些任務多是並行執行的,也多是串行執行的,和CPU核心數無關,是操做系統進程調度和CPU上下文切換達到的結果
  • 解決大併發的一個思路是將大任務分解成多個小任務:
    • 可能要使用一些數據結構來避免切分紅多個小任務帶來的問題
    • 能夠多進程/多線程並行的方式去執行這些小任務達到高效率
    • 或者以單進程/單線程配合多路複用執行這些小任務來達到高效率
相關文章
相關標籤/搜索