饑餓是指并發進程無法獲得執行工作所需的任何資源的情況。
當我們討論活鎖時,每個goroutine所缺乏的資源就是一個共享鎖。 活鎖需要與饑餓分開討論,因為在活鎖過程中,所有并發進程都是平等的,并且沒有任何任務可以被完成。 更廣泛地說,饑餓通常意味著有一個或多個貪婪的并發進程不公平地阻止一個或多個并發進程盡可能有效地完成工作,或者根本不可能完成工作。
下面這個例子展示了一個貪婪的goroutine和一個知足的goroutine:
```
var wg sync.WaitGroup
var sharedLock sync.Mutex
const runtime = 1*time.Second
greedyWorker := func() {
defer wg.Done()
var count int
for begin := time.Now(); time.Since(begin) <= runtime; {
sharedLock.Lock()
time.Sleep(3*time.Nanosecond)
sharedLock.Unlock()
count++
}
fmt.Printf("Greedy worker was able to execute %v work loops\n", count)
}
politeWorker := func() {
defer wg.Done()
var count int
for begin := time.Now(); time.Since(begin) <= runtime; {
sharedLock.Lock()
time.Sleep(1*time.Nanosecond)
sharedLock.Unlock()
sharedLock.Lock()
time.Sleep(1*time.Nanosecond)
sharedLock.Unlock()
sharedLock.Lock()
time.Sleep(1*time.Nanosecond)
sharedLock.Unlock()
count++
}
fmt.Printf("Polite worker was able to execute %v work loops.\n", count)
}
wg.Add(2)
go greedyWorker()
go politeWorker()
wg.Wait()
```
這個代碼段會輸出:
```
Polite worker was able to execute 289777 work loops. Greedy worker was able to execute 471287 work loops
```
greedy 貪婪地持有整個工作循環的共享鎖,而polite 試圖只在需要時才鎖定。 二者都進行了相同數量的模擬工作(休眠時間為三納秒),但正如你在相同的時間內看到的那樣,greedy 幾乎完成了兩倍的工作量!
但在這里我們要清楚的了解到,greedy不必要的擴大了對共享鎖的控制,并且(通過饑餓)阻礙了polite有效的執行。
我們在例子中使用計數的方式識別饑餓,在記錄和抽樣度量指標時這是一個很不錯的方法。檢測和解決饑餓的方法之一就是就是記錄程序完成的時間,然后確定你的程序執行速度是否與預期的一樣高。
> 值得一提的是,前面的代碼示例也可以作為進行同步內存訪問的性能分支示例。 因為同步訪問內存的代價很高,所以擴大我們的鎖定范圍可能會產生額外的代價。 另一方面,正如我們所看到的那樣,我們冒著令其他并發進程挨餓的風險。
> 如果你利用內存訪問同步,你必須在性能粗粒度同步和公平性細粒度同步之間找到平衡點。 當開始調試應用程序時,我強烈建議你將內存訪問同步僅限于程序的關鍵部分; 如果同步成為性能問題,則可以擴大范圍。 除此之外,其他的解決方式可能會更難以操作。
因此,饑餓可能會導致程序無效或不正確。 前面的例子表明了執行效率是如何被降低的,如果你有一個非常貪婪的并發進程,以至于完全阻止另一個并發進程完成工作,那么你的問題就大了。
我們還需要考慮來自程序之外導致的饑餓問題。請記住,饑餓還可以產生于于CPU,內存,文件句柄和數據庫連接:任何必須共享的資源都是饑餓的候選對象。
* * * * *
學識淺薄,錯誤在所難免。我是長風,歡迎來Golang中國的群(211938256)就本書提出修改意見。
感謝beego群(258969317)的"赤腳大仙"提出代碼部分格式化的修改意見,代碼格式缺失已作調整。
- 前序
- 誰適合讀這本書
- 章節導讀
- 在線資源
- 第一章 并發編程介紹
- 摩爾定律,可伸縮網絡和我們所處的困境
- 為什么并發編程如此困難
- 數據競爭
- 原子性
- 內存訪問同步
- 死鎖,活鎖和鎖的饑餓問題
- 死鎖
- 活鎖
- 饑餓
- 并發安全性
- 優雅的面對復雜性
- 第二章 代碼建模:序列化交互處理
- 并發與并行
- 什么是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運行時
- 任務調度