1. 程式人生 > >還在疑惑並發和並行?

還在疑惑並發和並行?

話題 設計 單線程 ble 如何 neo lang parallel 無需

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 在重疊的時間段內執行,所以這是一個支持並發的設計。與並行不同,單核單線程能支持並發。

經常看到這樣一個說法,叫做並發執行。現在我們可以正確理解它。有兩種可能:

  1. 原本想說的是“並行執行”,但是用錯了詞
  2. 指多個操作可以在重疊的時間段內進行,即,真的並行,或是類似上圖那樣的執行模式。

我的建議是盡可能不使用這個詞,容易造成誤會,尤其是對那些並發並行不分的人。但是讀到這裏的各位顯然能正確區分,所以下面為了簡便,將使用並發執行這個詞。

第二個重點是“可以在重疊的時間段內進行”中的“可以”兩個字。“可以”的意思是,正確的並發設計使並發執行成為可能,但是程序在實際運行時卻不一定會出現多個任務執行時間段 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.

還在疑惑並發和並行?