26. Go 語言中通道死鎖經典錯誤案例詳解

Hi,大家好,我是明哥。

在自己學習 Golang 的這段時間裏,我寫了詳細的學習筆記放在我的個人微信公衆號 《Go編程時光》,對於 Go 語言,我也算是個初學者,因此寫的東西應該會比較適合剛接觸的同學,如果你也是剛學習 Go 語言,不防關注一下,一起學習,一起成長。

我的在線博客:http://golang.iswbm.com
我的 Github:github.com/iswbm/GolangCodingTime


剛接觸 Go 語言的信道的時候,經常會遇到死鎖的錯誤,而導致這個錯誤的原因有很多種,這裏整理了幾種常見的。

fatal error: all goroutines are asleep - deadlock!

錯誤示例一

看下面這段代碼

package main

import "fmt"

func main() {
	pipline := make(chan string)
	pipline <- "hello world"
	fmt.Println(<-pipline)
} 

運行會拋出錯誤,如下

fatal error: all goroutines are asleep - deadlock!

看起來好像沒有什麼問題?先往信道中存入數據,再從信道中讀取數據。

回顧前面的基礎,我們知道使用 make 創建信道的時候,若不傳遞第二個參數,則你定義的是無緩衝信道,而對於無緩衝信道,在接收者未準備好之前,發送操作是阻塞的.

因此,對於解決此問題有兩種方法:

  1. 使接收者代碼在發送者之前執行
  2. 使用緩衝信道,而不使用無緩衝信道

第一種方法

若要程序正常執行,需要保證接收者程序在發送數據到信道前就進行阻塞狀態,修改代碼如下

package main

import "fmt"

func main() {
	pipline := make(chan string)
	fmt.Println(<-pipline)
    pipline <- "hello world"
} 

運行的時候還是報同樣的錯誤。問題出在哪裏呢?

原來我們將發送者和接收者寫在了同一協程中,雖然保證了接收者代碼在發送者之前執行,但是由於前面接收者一直在等待數據 而處於阻塞狀態,所以無法執行到後面的發送數據。還是一樣造成了死鎖。

有了前面的經驗,我們將接收者代碼寫在另一個協程裏,並保證在發送者之前執行,就像這樣的代碼

package main

func hello(pipline chan string)  {
	<-pipline
}

func main()  {
	pipline := make(chan string)
	go hello(pipline)
	pipline <- "hello world"
}

運行之後 ,一切正常。

第二種方法

接收者代碼必須在發送者代碼之前 執行,這是針對無緩衝信道纔有的約束。

既然這樣,我們改使用可緩衝信道不就OK了嗎?

package main

import "fmt"

func main() {
	pipline := make(chan string, 1)
	pipline <- "hello world"
	fmt.Println(<-pipline)
} 

運行之後,一切正常。

錯誤示例二

每個緩衝信道,都有容量,當信道里的數據量等於信道的容量後,此時再往信道里發送數據,就失造成阻塞,必須等到有人從信道中消費數據後,程序纔會往下進行。

比如這段代碼,信道容量爲 1,但是往信道中寫入兩條數據,對於一個協程來說就會造成死鎖。

package main

import "fmt"

func main() {
	ch1 := make(chan string, 1)

	ch1 <- "hello world"
	ch1 <- "hello China"

	fmt.Println(<-ch1)
}

錯誤示例三

當程序一直在等待從信道里讀取數據,而此時並沒有人會往信道中寫入數據。此時程序就會陷入死循環,造成死鎖。

比如這段代碼,for 循環接收了兩次消息("hello world"和“hello China”)後,再也沒有人發送數據了,接收者就會處於一個等待永遠接收不到數據的囧境。陷入死循環,造成死鎖。

package main

import "fmt"

func main() {
	pipline := make(chan string)
	go func() {
		pipline <- "hello world"
		pipline <- "hello China"
		// close(pipline)
	}()
	for data := range pipline{
		fmt.Println(data)
	}
}

包子鋪裏的包子已經賣完了,可還有人在排隊等着買,如果不再做包子,就要告訴排隊的人:不用等了,今天的包子已經賣完了,明日請早呀。

不能讓人家死等呀,不跟客人說明一下,人家還以爲你們店後面還在蒸包子呢。

所以這個問題,解決方法很簡單,只要在發送完數據後,手動關閉信道,告訴 range 信道已經關閉,無需等待就行。

package main

import "fmt"

func main() {
	pipline := make(chan string)
	go func() {
		pipline <- "hello world"
		pipline <- "hello China"
		close(pipline)
	}()
	for data := range pipline{
		fmt.Println(data)
	}
}

系列導讀

01. 開發環境的搭建(Goland & VS Code)

02. 學習五種變量創建的方法

03. 詳解數據類型:****整形與浮點型

04. 詳解數據類型:byte、rune與string

05. 詳解數據類型:數組與切片

06. 詳解數據類型:字典與布爾類型

07. 詳解數據類型:指針

08. 面向對象編程:結構體與繼承

09. 一篇文章理解 Go 裏的函數

10. Go語言流程控制:if-else 條件語句

11. Go語言流程控制:switch-case 選擇語句

12. Go語言流程控制:for 循環語句

13. Go語言流程控制:goto 無條件跳轉

14. Go語言流程控制:defer 延遲調用

15. 面向對象編程:接口與多態

16. 關鍵字:make 和 new 的區別?

17. 一篇文章理解 Go 裏的語句塊與作用域

18. 學習 Go 協程:goroutine

19. 學習 Go 協程:詳解信道/通道

20. 幾個信道死鎖經典錯誤案例詳解

21. 學習 Go 協程:WaitGroup

22. 學習 Go 協程:互斥鎖和讀寫鎖

23. Go 裏的異常處理:panic 和 recover

24. 超詳細解讀 Go Modules 前世今生及入門使用

25. Go 語言中關於包導入必學的 8 個知識點

26. 如何開源自己寫的模塊給別人用?

27. 說說 Go 語言中的類型斷言?

28. 這五點帶你理解Go語言的select用法


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章