當討論Go時,你會經常聽到人們圍繞CSP進行爭論。它會被稱為Go成功的原因,或者是并發編程的靈丹妙藥。雖然CSP使事情變得更容易,而且程序更加強大,但不幸的是這不是一個奇跡。 那它是什么?
CSP代表"Communicating Sequential Processes",它既是一種技術,也是引入它的論文的名稱。 1978年,Charles Antony Richard Hoare在計算機械協會(更通俗地稱為ACM)上發表了這篇論文。
在該論文中,Hoare認為輸入和輸出是兩個被忽視的編程原語,特別是在并發代碼中。在Hoare撰寫本文時,關于如何構造程序的研究仍在進行中,但大部分工作都是針對連續代碼的技術:goto語句的使用正在討論中,面向對象的思想開始萌發。并發并沒有得到太多關注。 Hoare開始糾正這個問題,于是他的論文和CSP誕生了。
在1978年的論文中,CSP只是一個簡單的編程語言,僅僅是為了展示順序過程的交流能力; 實際上,他甚至在論文中說過:
>因此,本論文中介紹的概念和符號......不應被視為適合用作編程語言,無論是抽象的還是具體的編程語言。
Hoare非常擔心他所提供的技術沒有進一步研究程序的正確性,而且這些技術可能不是以他自己的真實語言來表達的。 在接下來的六年里,CSP的概念被提煉成一種被稱為過程演算的形式化表示,以便采取交流順序過程的思想,并實際開始推理程序的正確性。 過程演算是數學建模并發系統的一種方式,也提供了代數法則來對這些系統進行轉換,以分析它們的各種屬性,例如效率和正確性。 雖然過程計算本身就是一個有趣的話題,但它們超出了本書的范圍。 而且由于關于CSP的原始文件和從其演變而來的語言在很大程度上是Go的并發模型的靈感,所以我們將重點關注這些。
為了支持他的觀點,Hoare的CSP程序設計語言包含原型來正確模擬輸入和輸出或進程之間的通信。Hoare將術語"進程"應用于邏輯的所有封裝部分,這些部分需要輸入來運行并產生其他過程將消耗的輸出。當他寫論文時,Hoare可能用“功能”這個詞來描述如何構建社區中的程序。
為了進行流程之間的溝通,Hoare創建了輸入和輸出命令 ! 用于將輸入發送到進程中,以及? 用于讀取進程的輸出。 每個命令都必須指定一個輸出變量(在從流程中讀取變量的情況下)或目標(在將輸入發送到進程的情況下)。 有時候這兩個過程會引用相同的東西,在這種情況下,這兩個過程將被認為是相對應的。 換句話說,一個進程的輸出將直接流入另一個進程的輸入。 下表給出了幾個例子。
| 表達式 | 說明 |
| --- | --- |
| cardreader?card image | 從cardreader讀取卡并將其值(字符數組)分配給變量cardimage |
| lineprinter!line image | 對于lineprinter,發送lineimage的值進行打印 |
| X?(x, y) | 從名為X的進程中,輸入一對值并將它們分配給x和y |
| DIV!(3*a+b, 13) | 從進程DIV輸出2個指定的值 |
| *[c:character; west?c → east!c] | 從west讀取所有字符并逐個放入east |
Go的通道與之相似之處很明顯。注意表格的最后一個例子中,來自west的輸出是如何發送給變量c的,并且輸入為east的輸入來自同一個變量,這兩個過程相對應。 在Hoare關于CSP的第一篇論文中,進程只能通過指定的來源和目的地進行通信。 他承認,這會導致代碼作為庫的嵌入問題,因為代碼的使用者必須知道輸入和輸出的名稱。 他同時提到注冊所謂的“端口名稱”的可能性,其中該名稱可以在并行命令的頭部聲明,我們可能會將其命名為命名參數并命名為返回值。
該語言還利用了所謂的守護命令,Edgar Dijkstra在1974年撰寫的一篇文章“Guarded commands, nondeterminacy and formal derivation of programs”中介紹了這一命令。 守護命令由→分割。 左側是右測的有條件的守護,如果左測是錯誤的,或者在命令的情況下返回假或退出,則右測永遠不會執行。 將這些與Hoare的I/O命令結合起來為Hoare的通信進程奠定了基礎,從而為Go的通道奠定了基礎。
通過使用這些原語,Hoare演示了幾個例子,并演示了一種支持通信建模的語言如何使解決問題變得更簡單,更容易理解。 他使用的一些符號有點簡單(perl程序員可能不同意),但他提出的問題有非常明確的解決方案。Go中的類似解決方案稍長一些,但也帶有這種清晰度。
歷史證明了Hoare是正確的; 然而,有趣的是,在Go發布之前,很少有語言確實將這些原語支持到語言中。 大多數流行的語言都傾向于共享和同步對CSP的信息傳遞風格的訪問。也有例外,但不幸的是這些僅限于沒有廣泛采用的語言。 Go是第一批將CSP原理融入其核心的語言之一,并將這種并發編程風格帶給了大眾。 它的成功使得其他語言也試圖添加這些原語。
內存訪問同步本質上并不壞。 我們將在后面的章節中(在“Go的并發哲學”一節)中指出,有時在某些情況下共享內存是合適的,即使在Go中也是如此。 但是,共享內存模型可能難以正確使用——特別是在大型或復雜的程序中。 正是因為這個原因,并發才被認為是Go的優勢之一:它從一開始就以CSP的原則為基礎,因此很容易閱讀,編寫和推理。
* * * * *
學識淺薄,錯誤在所難免。我是長風,歡迎來Golang中國的群(211938256)就本書提出修改意見。
- 前序
- 誰適合讀這本書
- 章節導讀
- 在線資源
- 第一章 并發編程介紹
- 摩爾定律,可伸縮網絡和我們所處的困境
- 為什么并發編程如此困難
- 數據競爭
- 原子性
- 內存訪問同步
- 死鎖,活鎖和鎖的饑餓問題
- 死鎖
- 活鎖
- 饑餓
- 并發安全性
- 優雅的面對復雜性
- 第二章 代碼建模:序列化交互處理
- 并發與并行
- 什么是CSP
- CSP在Go中的衍生物
- Go的并發哲學
- 第三章 Go的并發構建模塊
- Goroutines
- sync包
- WaitGroup
- Mutex和RWMutex
- Cond
- Once
- Pool
- Channels
- select語句
- GOMAXPROCS
- 結論
- 第四章 Go的并發編程范式
- 訪問范圍約束
- fo-select循環
- 防止Goroutine泄漏
- or-channel
- 錯誤處理
- 管道
- 構建管道的最佳實踐
- 便利的生成器
- 扇入扇出
- or-done-channel
- tee-channel
- bridge-channel
- 隊列
- context包
- 小結
- 第五章 可伸縮并發設計
- 錯誤傳遞
- 超時和取消
- 心跳
- 請求并發復制處理
- 速率限制
- Goroutines異常行為修復
- 本章小結
- 第六章 Goroutines和Go運行時
- 任務調度