转一篇关于并发和并行概念的好文,附带大神评论
轉自:https://laike9m.com/blog/huan-zai-yi-huo-bing-fa-he-bing-xing,61/
還在疑惑并發和并行?
OK,如果你還在為并發(concurrency)和并行(parallesim)這兩個詞的區別而感到困擾,那么這篇文章就是寫給你看的。搞這種詞語辨析到底有什么意義?其實沒什么意義,但是有太多人在混用錯用這兩個詞(比如遇到的某門課的老師)。不論中文圈還是英文圈,即使已經有數不清的文章在討論并行vs并發,卻極少有能講清楚的。讓一個講不清楚的人來解釋,比不解釋更可怕。比如我隨便找了個網上的解釋:
前者是邏輯上的同時發生(simultaneous),而后者是物理上的同時發生.
并發性(concurrency),又稱共行性,是指能處理多個同時性活動的能力,并發事件之間不一定要同一時刻發生。
并行(parallelism)是指同時發生的兩個并發事件,具有并發的含義,而并發則不一定并行。
來個比喻:并發和并行的區別就是一個人同時吃三個饅頭和三個人同時吃三個饅頭。
看了之后,你懂了么?不懂,更暈了。寫出這類解釋的人,自己也是一知半解,卻又把自己腦子里模糊的影像拿出來寫成文章,讓讀者閱畢反而更加疑惑。當然也有可能他確實懂了,但是寫出這種文字也不能算負責。至于本文,請相信,一定是準確的,我也盡量做到講解清晰。
OK,下面進入正題,concurrency vs parallesim
讓我們大聲朗讀下面這句話:
“并發”指的是程序的結構,“并行”指的是程序運行時的狀態
即使不看詳細解釋,也請記住這句話。下面來具體說說:
并行(parallesim)
這個概念很好理解。所謂并行,就是同時執行的意思,無需過度解讀。判斷程序是否處于并行的狀態,就看同一時刻是否有超過一個“工作單位”在運行就好了。所以,單線程永遠無法達到并行狀態。
要達到并行狀態,最簡單的就是利用多線程和多進程。但是 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.
2015.8.14 更新
事實上我之前的理解還是有錯誤。在《最近的幾個面試》這篇文章里有提到。最近買了《七周七并發模型》這本書,發現其中有講,在此摘錄一下(英文版 p3~p4):
Although there’s a tendency to think that parallelism means multiple cores,?modern computers are parallel on many different levels. The reason why individual cores have been able to get faster every year, until recently, is that they’ve been using all those extra transistors predicted by Moore’s law in parallel, both at the bit and at the instruction level.
Bit-Level Parallelism
Why is a 32-bit computer faster than an 8-bit one? Parallelism. If an 8-bit computer wants to add two 32-bit numbers, it has to do it as a sequence of 8-bit operations. By contrast, a 32-bit computer can do it in one step, handling each of the 4 bytes within the 32-bit numbers in parallel. That’s why the history of computing has seen us move from 8- to 16-, 32-, and now 64-bit architectures. The total amount of benefit we’ll see from this kind of parallelism has its limits, though, which is why we’re unlikely to see 128-bit computers soon.
Instruction-Level Parallelism
Modern CPUs are highly parallel, using techniques like pipelining, out-of-order execution, and speculative execution.
As programmers, we’ve mostly been able to ignore this because, despite the fact that the processor has been doing things in parallel under our feet, it’s carefully maintained the illusion that everything is happening sequentially. This illusion is breaking down, however. Processor designers are no longer able to find ways to increase the speed of an individual core. As we move into a multicore world, we need to start worrying about the fact that instructions aren’t handled sequentially. We’ll talk about this more in Memory Visibility, on page ?.
Data Parallelism
Data-parallel (sometimes called SIMD, for “single instruction, multiple data”) architectures are capable of performing the same operations on a large quantity of data in parallel. They’re not suitable for every type of problem, but they can be extremely effective in the right circumstances. One of the applications that’s most amenable to data parallelism is image processing. To increase the brightness of an image, for example, we increase the brightness of each pixel. For this reason, modern GPUs (graphics processing units) have evolved into extremely powerful data-parallel processors.
Task-Level Parallelism
Finally, we reach what most people think of as parallelism—multiple processors. From a programmer’s point of view, the most important distinguishing feature of a multiprocessor architecture is the memory model, specifically whether it’s shared or distributed.
最關鍵的一點是,計算機在不同層次上都使用了并行技術。之前我討論的實際上僅限于 Task-Level 這一層,在這一層上,并行無疑是并發的一個子集。但是并行并非并發的子集,因為在 Bit-Level 和 Instruction-Level 上的并行不屬于并發——比如引文中舉的 32 位計算機執行 32 位數加法的例子,同時處理 4 個字節顯然是一種并行,但是它們都屬于 32 位加法這一個任務,并不存在多個任務,也就根本沒有并發。
所以,正確的說法是這樣:
并行指物理上同時執行,并發指能夠讓多個任務在邏輯上交織執行的程序設計
按照我現在的理解,并發針對的是 Task-Level 及更高層,并行則不限。這也是它們的區別。
?
評論內容:
?
實際上個人覺得從計算機體系發展的角度來介紹更為合適,也更容易理解。
從Instruction-Level Parallelism來說,這個其實和Task-Level Parallelism是息息相關的。從早期的計算機來看,任何一個嚴格意義上的Task-Level Parallelism必然是對應著Instruction-Level Parallelism的,而Instruction-Level Parallelism必然要依賴于硬件的。這種觀點是很容易接受的,因為這種計算機實質上屬于批處理系統,執行時不存在外部干預,并且不存在程序設計語言,只有指令。
任何屬于馮諾依曼結構的計算機,其中的CPU(或者說核)必然是串行執行指令的。所以在任何單CPU機器上,是不存在嚴格意義上,或者說狹義上的并行的--在指令級別的嚴格意義上的并行,是指在一個足夠小的時刻,可以允許大于一條的指令在執行。
因此在早期的計算機中,實現嚴格意義上的并行只能采取增加計算機數量的方法。在這種情況下,Task-Level Parallelism和Instruction-Level Parallelism幾乎沒有區別。
想要在這種計算機上同時運行多道程序,歷史上采用的技術就是--多任務。
(這里說句題外話,如果大家看過早起計算機如何編寫指令,如何輸入指令,如何輸出--簡單來說,就是從搬紙帶到搬磁帶的進化,那么看gopher的那些關于并行并發的圖會好理解很多。這些圖簡直就是在向當時的搬磚工,啊不對計算機先驅們致敬啊。)
這里需要介紹一下為什么會有多任務。正如我們知道的,程序從大致可以分為計算密集型和IO密集型兩個極端,而商業程序大多比較偏向IO密集型,所以早期的計算機在很有限的計算資源下,就已經出現了IO和計算能力不匹配的問題,即在IO上花的時間要遠遠大于計算的時間。當然這個問題當時并沒有導致并發方案的出現,而是導致了一種硬件的并行--比如IBM 1401這樣的數據處理專用機的出現。當然之后在第三代計算機中出現了SPOOLing技術就不再需要這樣的機器了。
回到多任務上來。IO操作耗時較長,也并不需要占用CPU,但這時候CPU即使沒有在執行指令卻也必須等待輸入完畢,導致了CPU的空閑。在那個年代,計算資源大多是按時間售賣的,這種空閑是一種極大的浪費。那么我們為什么不能讓CPU在等待IO的這段時間里也去執行指令呢?這就很自然的引出了多任務(Multitasking)的概念了。這個概念的理念很好理解,即當一個程序進行IO或者其他耗時卻不占用CPU的操作時,將這個程序切換出去,去執行另一個程序的指令。之后等之前的程序IO完畢了,再切換回來繼續執行,以此防止計算資源的浪費。這種切換即上下文切換(context switch)。
事實上,這就是一種并發了。在那個時代的計算機上,因為計算資源的限制很可能導致單個任務的完成時間明顯大于預期,導致并發帶來的"并行感"還不是太強烈。但是在現代計算機上,這很容易造成一種兩個任務同時執行的錯覺。
因此我們可以很容易的看出,這里的并發本質上是一種多個操作者對于計算資源的共享使用,它的目的是達到減少的資源閑置導致的浪費--因此,自然會涉及到計算資源如何分配等問題,這就引申出調度和鎖等概念。事實上,如果將“計算”兩字去掉,這個描述就可以擴展到計算機中任何對于資源的共享使用,包括了各種層級的方方面面,比如在中文件,內存頁面中,進程線程中等。
當然,以上的并發是從一種常見的實現的角度來描述的。正如博主所說,我們可以用真正的并行來實現并發,比如在雙核CPU上每個核里跑一個獨立的進程。我們也可以在一個單核的CPU上通過程序調度來實現這兩個進程的并發。
而嚴格意義上的并行,在指令水平,必然只能指同一時刻有多于一條指令處于執行階段,因此就必須得“We move into a multicore world”才行。。即使例子中的pipelining,對于單核的CPU也并非實現并行,而只是讓每個時刻,IF, ID, EX, MA, WB這五個步驟對應的回路單元都不會閑置而已--事實上這個理念就是并發了,正如博主所說的“并發設計往往需要把流程拆開,是因為如果不拆分也就不可能在同一時間段進行多個任務了。這種拆分可以是平行的拆分,比如抽象成同類的任務,也可以是不平行的,比如分為多個步驟。”--因此,并發并不僅限于程序設計的
Task-Level上,它是關于資源共享和使用問題的解決方法的一種抽象。
-
把加法看成多個任務的話,也可以說有并發了。所以其實本質在于站在哪個層次來看。?
-
非常對,把加法這個任務分成多個子任務就可以視作并發了。其實主要還是因為這兩個詞都有狹義和廣義的解釋的原因。如果從廣義上來說,并行必然是并發,因為廣義的并發的高度抽象定義可以說是,多個問題或者問題的幾個部分在一個重疊的時間段內被解決。
不過個人還是喜歡用并發來表示通過調度器以及上下文切換等實現的多任務或者其他類似的技術和手段(其實是不知道用什么詞代替好),用并行來表示嚴格的物理上的并行。
-
-
?
轉載于:https://www.cnblogs.com/mysic/p/5633860.html
總結
以上是生活随笔為你收集整理的转一篇关于并发和并行概念的好文,附带大神评论的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 做梦梦到门牙掉了一半是什么意思
- 下一篇: 梦到获奖意味着什么