Go語言高能踩坑記錄:信道

使用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)
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章