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

Hi,你們好,我是明哥。git

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

個人在線博客: http://golang.iswbm.com
個人 Github:github.com/iswbm/GolangCodingTime

剛接觸 Go 語言的信道的時候,常常會遇到死鎖的錯誤,而致使這個錯誤的緣由有不少種,這裏整理了幾種常見的。golang

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. 使用緩衝信道,而不使用無緩衝信道

第一種方法spa

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

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用法


相關文章
相關標籤/搜索