並發與並行是兩個既相似而又不相同的概念,但往往容易混為一談,這兩者究竟有什么區別呢?本文通過一個例子讓你更好地理解(本文由並發編程網翻譯)。
現代社會是並行的:多核、網絡、雲計算、用戶負載,並發技術對此有用。
Go語言支持並發,它提供了:並發執行(goroutines),同步和消息(channels)和多路並發控制(select)。
並發和並行的區別
當Go聲稱是並發時,人們說:“並發很酷!耶,我可以並行運行了!”,但這是個錯誤的。因為很多人都不了解他們間的差別。“我用四個處理器來做質數篩選,但是更慢了。”
- 並發(Concurrency):以可獨立執行的進程集合的方式編程(進程是出了名的難定義,這里是通常意義上的進程,不是Linux進程)
- 並行(Parallelism):以可同時執行的(可能相關的)計算指令方式編程。
兩者的區別:並發是同時處理(dealing)很多的事情,並行是同時做(doing)很多的事情。不同,但也相關。一個是關於代碼結構,一個是關於代碼執行。並發為可能的(不是必須的)並行問題提供了一種解決方案。比如:
- 鼠標、鍵盤、顯示器、磁盤驅動是並發結構的。
- 向量點積是並行的。
並發帶有通信
並發是一種構造程序的方式,把任務分解為一個個獨立運行的小任務。通信是協調這些小任務的手段。
Go 的模型(還有Erlang等)都是基於CPS(Communicating sequential processes,通信順序進程):其論文C. A. R. Hoare: Communicating Sequential Processes (CACM 1978)
通過一個例子來理解
以上講得太抽象了,我們舉實際點的例子。
我們的問題:把一堆廢棄語言的說明書運到火爐里,一只地鼠會花費很長時間。
更多的地鼠
單單更多的地鼠也不解決問題,它們需要更多的推車。
更多的地鼠、更多的推車
這樣會加快速度,但它們會在那堆書和爐子那邊遇上瓶頸。同時也要同步兩只地鼠,可以通過消息的方式實現。
全部加倍
這樣會以兩倍的速度運送。這是兩個地鼠程序的並發構成(concurrent composition)。
但這種設計不是自發並行的,如果一次只有一只地鼠在運會怎樣?
這種設計仍是並發,不是並行。[譯者注:一只地鼠運一次上面那堆書,然后第二只地鼠再運一次下面那堆書。一次只允許一只地鼠運送,這樣就不是並行的。]
然而,這種場景是可以自發並行的。並發構成可以考慮下其他模型。
另一種設計
三只地鼠在工作,但可能會有延誤。每只地鼠是個獨立的步驟,附加協調(通信)。
更細粒度的並發
增加一只地鼠用來運回空推車。四只地鼠在工作,運行得更加流暢,每只地鼠都在做一個簡單的任務。
如果我們把事情安排的足夠好(現實中很難但不是不可能),速度會是最先只有一只地鼠的那個設計的四倍。
觀察結論:我們在一個已有的設計(指三個地鼠的那個設計)中添加一個並發的步驟(第四只地鼠)增強了系統的性能。更多的地鼠干了更多的活,系統運行得更好。並發比簡單的並行對問題要有更深的洞察。
我們有四個並發的步驟:1.裝書到推車上2.把推車運到火爐邊3.把書卸到火爐里4.運回空推車
不同的並發設計能以不同的方式來並行。
更多的並行
我們以另一個維度來並行,並行使這樣的設計變的容易。八只地鼠,都在繁忙工作。
但也可能根本沒有並行
謹記:即使一次只能有一只地鼠在工作(零並行),這也不失為一個良好的並發的解決方案。
另一種設計
下面也是一種用並發組成來解決問題的設計。兩只地鼠,再加上一個中轉堆。
以一般的方式來並行
用更多的並發程序來提高吞吐量
或者一種不同的方式
在多地鼠並發模型中引入中轉堆
全面優化
使用我們所有的技術,16只地鼠都開足馬力。
學到內容
我們有很多方法把問題分解,這才是並發設計。一旦我們分解了問題,並行就自然而然的產生了,正確性也變得很容易。
回到計算
我們關於運書的問題,可以看做是如下的類比:書堆是Web數據,地鼠是CPU,推車是調度、渲染或是網絡,火堆是代理、瀏覽器或是其他的消費者。地鼠提供網絡數據,這就是一個可擴展的Web服務的並發設計了。
