死鎖是所有并發進程都在彼此等待的狀態。 在這種情況下,如果沒有外部干預,程序將永遠不會恢復。
如果這聽起來很嚴峻,那是因為它確實很嚴峻! Go運行時會檢測到一些死鎖(所有的例程必須被阻塞或“休眠”),但這對于幫助你防止死鎖產生沒有多大幫助。
為了幫助你更直觀的認識死鎖,我們先來看一個例子。同樣的,跟著注釋走,任何變量、函數、語句都不重要:
```
type value struct {
mu sync.Mutex
value int
}
var wg sync.WaitGroup
printSum := func(v1, v2 *value) {
defer wg.Done()
v1.mu.Lock() //1
defer v1.mu.Unlock() //2
time.Sleep(2 * time.Second) //3
v2.mu.Lock()
defer v2.mu.Unlock()
fmt.Printf("sum=%v\n", v1.value+v2.value)
}
var a, b value
wg.Add(2)
go printSum(&a, &b)
go printSum(&b, &a)
wg.Wait()
```
1. 這里我們試圖訪問帶鎖的部分
2. 這里我們試圖調用defer關鍵字釋放鎖
3. 這里我們添加休眠時間 以造成死鎖
如果你試著運行這段程序,應該會看到這樣的輸出:
```
fatal error: all goroutines are asleep - deadlock!
```
為什么? 如果仔細觀察,你將在此代碼中看到計時問題。下面的時序圖能清晰的展現問題所在:
:-: 
實質上,我們創建了兩個不能一起運轉的齒輪:我們的第一個打印總和調用a鎖定,然后嘗試鎖定b,但與此同時,我們打印總和的第二個調用鎖定了b并嘗試鎖定a。 兩個goroutine都無限地等待著彼此。
>為了保持這個例子簡單,我使用time.Sleep來觸發死鎖。 但是,這引入了競爭條件! 你能找到它嗎?
>一個邏輯上“完美”的死鎖將需要正確的同步。
這似乎很明顯,為什么當我們以這種方式繪制圖表時出現這種僵局,但我們會從更嚴格的定義中受益。事實證明,出現僵局時必定存在一些條件,1971年,埃德加科夫曼在一篇論文中列舉了這些條件。這些條件現在稱為科夫曼條件,是幫助檢測,防止和糾正死鎖的技術基礎。
科夫曼條件如下:
#### *相互排斥*
并發進程在任何時候都擁有資源的獨占權。
#### *等待條件*
并發進程必須同時持有資源并等待額外的資源。
#### *沒有搶占*
并發進程持有的資源只能由該進程釋放,因此它滿足了這種情況。
#### *循環等待*
并發進程(P1)等待并發進程(P2),同時P2也在等待P1,因此也符合"循環等待"這一條件。
:-: 
讓我們來看看我們的設計程序,并確定它是否符合所有四個條件:
1. printSum函數確實需要a和b的獨占權,所以它滿足了這個條件。
2. 因為printSum保持a或b并等待另一個,所以它滿足這個條件。
3. 我們沒有任何辦法讓我們的goroutine被搶占。
4. 我們第一次調用printSum正在等待我們的第二次調用,反之亦然。
很好,我們親手實現了死鎖。
科夫曼條件同樣有助于我們規避死鎖。如果我們確保至少有一個條件不成立,就可以防止發生死鎖。不幸的是,實際上這些條件很難推理,因此難以預防。網上大量充斥著被死鎖困擾的開發人員的求助,一旦有人指出它就很明顯,但通常需要另一雙眼睛。
* * * * *
學識淺薄,錯誤在所難免。我是長風,歡迎來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運行時
- 任務調度