最后,我們談到了開發并發代碼的最困難的方面,它是所有其他問題的基礎:人。 每行代碼的編寫者至少有一個人。
正如我們發現的,并發代碼的難題由很多原因產生。 如果你是一名開發人員,并且在引入新功能時嘗試解決所有這些問題,或修復程序中的錯誤,確定什么樣的操作是正確的確實很困難。
如果你從零開始構建程序,需要建立一個合理的方式來模擬問題,但如果涉及到并發,就可能很難找到合適的抽象級別。你該如何向調用者暴露并發接口?應該使用什么樣的技術使之簡單而有效?應該支持什么樣的并發規模?有不同的結構化方式來思考這些問題,但這些問題的解決方案有時候更接近藝術而不是技術。
作為一個對已有代碼改造的開發人員,哪些代碼利用并發并不總是很明顯,如何安全地使用前人的代碼有時更與智力無關。考慮下面的函數聲明:
```
// CalculatePi 會在開始和結束位置之間計算Pi的數字
func CalculatePi(begin, end int64, pi *Pi)
```
以較高精度計算pi是最好的方法,但這個例子引發了很多問題:
* 我該如何調用這個函數?
* 我是否負責實例化此函數的多個并發調用?
* 看起來函數的所有實例都將直接在我傳入地址的Pi實例上運行; 是由我負責同步對內存的訪問,還是函數為我處理?
僅此一個函數就引發了這些問題。 想象一下任何規模適中的程序,你就可以開始理解并發可能帶來的復雜性。
注釋可以在這里創造奇跡。如果函數是這樣寫的呢?
```
// CalculatePi 會在開始和結束位置之間計算Pi的數字
//
// 在內部,CalculatePi會創建FLOOR((end-begin)/ 2)遞歸調用
// CalculatePi的并發進程。 寫入pi的同步鎖由Pi結構內部處理。
func CalculatePi(begin, end int64, pi *Pi)
```
我們現在明白,調用者可以簡單地調用該函數,而不必擔心訪問控制或同步問題。 重要的是,注釋涵蓋了這些方面:
* 誰負責并發?
* 問題空間如何映射到并發基元?
* 誰負責同步?
當需要暴露涉及并發問題的函數、方法和變量時,請盡可能讓你的同事和未來的自己受益:不一定非要寫出冗長的注釋,但請盡量覆蓋上面的三個要素。
還要考慮到函數命名在含義上的模糊。也許我們應該讓函數看起來沒有副作用:
```
func CalculatePi(begin, end int64) []uint
```
這個函數的簽名本身就消除了任何同步問題的疑問,但仍然留下了是否使用并發的問題。 我們可以再次修改簽名,以明確的告訴調用者我們要返回什么:
```
func CalculatePi(begin, end int64) <-chan uint
```
現在我們首次看到了被稱為channel(通道)的用法。隨后在第三章會有更詳細的介紹。修改后的函數簽名表明CalculatePi將至少有一個goroutine,我們不應該為創建自己的goroutine而煩惱。
然后,這些修改會產生性能影響,必須予以考慮,我們又回到了平衡清晰度與性能之間的問題。 清晰性非常重要,因為我們希望將來盡可能使用此代碼的人能夠做正確的事情,并且由于顯而易見的原因,性能很重要。 兩者不是相互排斥的,但它們很難同時被處理的很好。
體會下我們在上面遇到的各種困難,并嘗試將它們擴展到團隊規模。
喔,真是個相當可怕的情景。
好消息是,Go已經逐步的給出了簡單實用的解決方案。語言本身就具備了較強的可讀性而又不失簡約。Go鼓勵并發建模的正確性,可組合性和可伸縮性。事實上,Go處理并發的方式實際上可以幫助你更清楚地表達問題。 讓我們來看看為什么這么說。
* * * * *
學識淺薄,錯誤在所難免。我是長風,歡迎來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運行時
- 任務調度