OK,如果你還在為并發(concurrency)和并行(parallelism)這兩個詞的區別而感到困擾,那么這篇文章就是寫給你看的。搞這種詞語辨析到底有什么意義?其實沒什么意義,但是有太多人在混用錯用這兩個詞(比如遇到的某門課的老師)。不論中文圈還是英文圈,即使已經有數不清的文章在討論并行vs并發,卻極少有能講清楚的。讓一個講不清楚的人來解釋,比不解釋更可怕。比如我隨便找了個網上的解釋
前者是邏輯上的同時發生(simultaneous),而后者是物理上的同時發生.
并發性(concurrency),又稱共行性,是指能處理多個同時性活動的能力,并發事件之間不一定要同一時刻發生。
并行(parallelism)是指同時發生的兩個并發事件,具有并發的含義,而并發則不一定并行。
來個比喻:并發和并行的區別就是一個人同時吃三個饅頭和三個人同時吃三個饅頭。
看了之后,你懂了么?不懂,更暈了。寫出這類解釋的人,自己也是一知半解,卻又把自己腦子里模糊的影像拿出來寫成文章,讓讀者閱畢反而更加疑惑。當然也有可能他確實懂了,但是寫出這種文字也不能算負責。至于本文,請相信,一定是準確的,我也盡量做到講解清晰。
OK,下面進入正題,concurrency vs parallelism
讓我們大聲朗讀下面這句話:
“并發”指的是程序的結構,“并行”指的是程序運行時的狀態
即使不看詳細解釋,也請記住這句話。下面來具體說說:
并行(parallelism)
這個概念很好理解。所謂并行,就是同時執行的意思,無需過度解讀。判斷程序是否處于并行的狀態,就看同一時刻是否有超過一個“工作單位”在運行就好了。所以,單線程永遠無法達到并行狀態。
要達到并行狀態,最簡單的就是利用多線程和多進程。但是 Python 的多線程由于存在著名的 GIL,無法讓兩個線程真正“同時運行”,所以實際上是無法到達并行狀態的。
并發(concurrency)
要理解“并發”這個概念,必須得清楚,并發指的是程序的“結構”。當我們說這個程序是并發的,實際上,這句話應當表述成“這個程序采用了支持并發的設計”。好,既然并發指的是人為設計的結構,那么怎樣的程序結構才叫做支持并發的設計?
正確的并發設計的標準是:使多個操作可以在重疊的時間段內進行(two tasks can start, run, and complete in overlapping time periods)。
這句話的重點有兩個。我們先看“(操作)在重疊的時間段內進行”這個概念。它是否就是我們前面說到的并行呢?是,也不是。并行,當然是在重疊的時間段內執行,但是另外一種執行模式,也屬于在重疊時間段內進行。這就是協程。
使用協程時,程序的執行看起來往往是這個樣子:
task1, task2 是兩段不同的代碼,比如兩個函數,其中黑色塊代表某段代碼正在執行。注意,這里從始至終,在任何一個時間點上都只有一段代碼在執行,但是,由于 task1 和 task2 在重疊的時間段內執行,所以這是一個支持并發的設計。與并行不同,單核單線程能支持并發。
經常看到這樣一個說法,叫做并發執行。現在我們可以正確理解它。有兩種可能:
- 原本想說的是“并行執行”,但是用錯了詞
- 指多個操作可以在重疊的時間段內進行,即,真的并行,或是類似上圖那樣的執行模式。
我的建議是盡可能不使用這個詞,容易造成誤會,尤其是對那些并發并行不分的人。但是讀到這里的各位顯然能正確區分,所以下面為了簡便,將使用并發執行這個詞。
第二個重點是“可以在重疊的時間段內進行”中的“可以”兩個字。“可以”的意思是,正確的并發設計使并發執行成為可能,但是程序在實際運行時卻不一定會出現多個任務執行時間段 overlap 的情形。比如:我們的程序會為每個任務開一個線程或者協程,只有一個任務時,顯然不會出現多個任務執行時間段重疊的情況,有多個任務時,就會出現了。這里我們看到,并發并不描述程序執行的狀態,它描述的是一種設計,是程序的結構,比如上面例子里“為每個任務開一個線程”的設計。并發設計和程序實際執行情況沒有直接關聯,但是正確的并發設計讓并發執行成為可能。反之,如果程序被設計為執行完一個任務再接著執行下一個,那就不是并發設計了,因為做不到并發執行。
那么,如何實現支持并發的設計?兩個字:拆分。
之所以并發設計往往需要把流程拆開,是因為如果不拆分也就不可能在同一時間段進行多個任務了。這種拆分可以是平行的拆分,比如抽象成同類的任務,也可以是不平行的,比如分為多個步驟。
并發和并行的關系
Different concurrent designs enable different ways to parallelize.
這句話來自著名的talk: Concurrency is not parallelism。它足夠concise,以至于不需要過多解釋。但是僅僅引用別人的話總是不太好,所以我再用之前文字的總結來說明:并發設計讓并發執行成為可能,而并行是并發執行的一種模式。
最后,關于Concurrency is not parallelism這個talk再多說點。自從這個talk出來,直接引爆了一堆討論并發vs并行的文章,并且無一例外提到這個talk,甚至有的文章直接用它的slide里的圖片來說明。比如這張:
以為我要解釋這張圖嗎?NO。放這張圖的唯一原因就是萌萌的gopher。
再來張特寫:
之前看到知乎上有個關于go為什么流行的問題,有個答案是“logo萌”當時我就笑噴了。
好像跑題了,繼續說這個 talk。和很多人一樣,我也是看了這個 talk 才開始思考 concurrency vs parallesim 的問題。為了研究那一堆推小車的 gopher 到底是怎么回事,我花費了相當多的時間。實際上后來我更多地是通過網上的只言片語(比如SO的回答)和自己的思考弄清了這個問題,talk 并沒有很大幫助。徹底明白之后再回過頭來看這個 talk,確實相當不錯,Andrew Gerrand 對這個問題的理解絕對夠深刻,但是太不新手向了。最大問題在于,那一堆 gopher 的例子不夠好,太復雜。Andrew Gerrand 花了大把時間來講述不同的并發設計,但是作為第一次接觸這個話題的人,在沒有搞清楚并發并行區別的情況下就去研究推小車的 gopher,太難了。“Different concurrent designs enable different ways to parallelize” 這句總結很精辟,但也只有那些已經透徹理解的人才能領會,比如我和看到這里的讀者,對新手來說就和經文一樣難懂。總結下來一句話,不要一開始就去看這個視頻,也不要花時間研究推小車的gopher。Gopher is moe, but confusing.
最關鍵的一點是,計算機在不同層次上都使用了并行技術。之前我討論的實際上僅限于 Task-Level 這一層,在這一層上,并行無疑是并發的一個子集。但是并行并非并發的子集,因為在 Bit-Level 和 Instruction-Level 上的并行不屬于并發——比如引文中舉的 32 位計算機執行 32 位數加法的例子,同時處理 4 個字節顯然是一種并行,但是它們都屬于 32 位加法這一個任務,并不存在多個任務,也就根本沒有并發。
所以,正確的說法是這樣:
并行指物理上同時執行,并發指能夠讓多個任務在邏輯上交織執行的程序設計
按照我現在的理解,并發針對的是 Task-Level 及更高層,并行則不限。這也是它們的區別。
原文:https://laike9m.com/blog/huan-zai-yi-huo-bing-fa-he-bing-xing,61/