使用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 創建信道的時候,若不傳遞第二個參數,則你定義的是無緩衝信道,而對於無緩衝信道,在接收者(stdout)未準備好之前,發送操作是阻塞的。因此,對於解決此問題有兩種方法:
第一種方法:使接收者代碼在發送者之前執行
若要程序正常執行,需要保證接收者程序在發送數據到信道前就進行阻塞狀態(準備好),修改代碼如下
package main
import "fmt"
func main() {
pipline := make(chan string)
fmt.Println(<-pipline)
pipline <- "hello world"
}
然而運行的時候還是報同樣的錯誤。問題出在哪裏呢?原來我們將發送者和接收者寫在了同一協程中,雖然保證了接收者代碼在發送者之前執行,但是由於前面接收者一直在等待數據 而處於阻塞狀態,所以無法執行到後面的發送數據。還是一樣造成了死鎖。
這樣的話,我們可以將接收者代碼寫在另一個協程裏,並保證在發送者之前執行,就像這樣:
package main
import "fmt"
func hello(pipline chan string) {
fmt.Println(<-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"
}()
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)
}
}