你可能會也,可能不會發現所有這些引人入勝的東西,但如果你正在閱讀本書,你就有難以解決的問題,并且你想知道為什么這些問題很重要。 Go的做法有何不同,使它在并發性方面與其他流行語言不同?
我們在"并發與并行"章節提到,語言在操作系統線程和內存訪問同步級別結束其抽象鏈是很常見的。Go采用不同的路線,用goroutines和channel的概念取而代之。
如果我們在抽象并發代碼的兩種方式中比較概念,我們可能會將goroutine與線程和通道相比較(這些基元只具有相似性,但希望能夠進行比較幫助你獲得更深入的理解)。 這些不同的抽象為我們做了什么?
Goroutines使我們不必從并行的角度思考我們的問題,而是讓我們對問題進行模擬,使其更接近自然。 雖然我們討論了并發和并行之間的區別,但這種差異如何影響我們對解決方案的建模可能并不明確。 我們來看一個例子。
比方說,我需要構建一個Web服務器,在客戶端提交訪問請求。 暫時擱置框架,用一種只提供線程抽象的語言,我可能會反思下列問題:
* 我的語言是否自然地支持線程,還是必須選擇一個庫?
* 我的線程限制邊界應該在哪里?
* 此操作系統中的線程有多重?
* 我的程序將如何在處理線程中運行不同的操作系統?
* 我應該創建一個線程池來限制我創建的線程數量。 我如何找到最佳的數字?
所有這些都是需要考慮的重要事情,但是沒有一個直接關注你正在嘗試解決的問題。 你被放到了如何解決并行性問題的技術上。
如果我們退后一步并考慮原始問題,我們可以這樣說:個人用戶正在連接到我的終端并開啟會話。 會話應該對他們的請求返回響應。 在Go中,我們幾乎可以用代碼直接表示這個問題:我們將為每個傳入連接創建一個goroutine,在那里將請求放在那里(可能與其他數據/服務的goroutines進行通信),然后從 goroutine的函數返回信息。使用Go可以讓我們自然地思考這個問題并直接映射到編碼。
這是通過Go對我們的承諾實現的:goroutines是輕量級的,我們通常不必擔心創建一個導致耗費很多資源。有時候需要考慮系統中有多少個goroutines正在運行,但是這么做是一個過早的優化。 將此與線程進行對比,你可以預先考慮這些問題。
不過這并不意味著這種對并發問題建模不重要。在使用Go的情況下,語言是圍繞并發設計的,所以這種語言與它提供的并發原語是一致的。 這意味著更少的摩擦和更少的錯誤。
對問題的更直觀自然的映射處理好處是巨大的,它也有一些有益的副作用。Go的運行時自動將goroutine多路復用到OS線程,并為我們管理其調度。 這意味著可以在不需要改變我們如何模擬問題的情況下對運行時進行優化; 這是經典的關注點分離。 隨著并行性的進步,Go的運行時在更新,程序的性能也在提高。 留意Go的發布說明,偶爾你會看到類似的東西:
> 在Go 1.5中,goroutines的調度順序已經改變。
并發和并行的分離還有另一個好處:由Go的運行時為你管理goroutines的調度,它可以檢查像阻塞等待I/O的goroutines和智能地重新分配OS線程到未阻塞的goroutines之類的事情。 這也增加了你的代碼的性能。我們將在第六章中更多地討論Go的運行時為你做什么。
現實問題和Go代碼之間更自然映射的另一個好處是提高了以并發方式建模的問題數量。 由于我們作為開發人員所面臨的問題在現實中是并發的,使用Go可以在更細的粒度級別上編寫并發代碼,而不是我們在其他語言中可能會使用的思考方式;例如,回到我們的Web服務器示例,我們現在將為每個用戶建立一個goroutine,而不是將多個連接復用到一個線程池中。 這種更精細的粒度使程序能夠在主機上實現友好的并行擴展。
goroutine是Go提供的解決方案的一部分,從CSP概念衍生出的通道——channel和select語句同樣有用。
例如,通道本身可與其他通道合并。 這使得編寫大型系統變得更簡單,因為莫可以通過組合輸出來協調多個子系統的輸入。 可以將輸入通道與超時,執行取消或把消息組合到其他子系統。 與之相對應的,協調互斥是一個值得關注的話題。
select語句是Go的通道的補充,并且是賦予通道巨大威力的直接因素。 select語句允許你有效地等待事件,以統一的隨機方式從競爭渠道中選擇消息,如果沒有消息等待,則繼續操作。
這些由CSP和支持它的運行時所激發的奇妙基元就是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運行時
- 任務調度