簡體   English   中英

go通道死鎖問題

[英]go channel deadlock issue

剛開始學習golang,並沒有完全理解死鎖是如何發生的。 這是改編自 golang playground 教程的示例:

 package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case q:= <-quit:
            fmt.Println(q)
            return
        }
    }
}
func pp(c chan int, quit chan int){
   for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
}

func main() {
    c := make(chan int)
    quit := make(chan int)
   // here it's good, no deadlock
     go pp(c,quit)    
     fibonacci(c, quit)
   // but if change the order of above two line:
   // fibonacci(c,quit)
   // go pp(c,quit)
   // it will deadlock
}

為什么上面兩行的順序很重要?

您有兩個函數,它們需要同時運行才能使通道通信正常工作——一個必須在接收的同時另一個在發送。 在這種情況下:

 go pp(c,quit)    
 fibonacci(c, quit)

您將pp作為 goroutine 啟動,它開始運行,然后您調用fibonacci ,這樣兩者都在運行,並且一切正常。 如果您按照您的建議將其更改為:

 fibonacci(c, quit)
 go pp(c,quit)    

然后您將fibonacci作為常規函數調用,而不是作為 goroutine,這意味着在fibonacci返回之前不會執行下一行。 因為fibonacci期望從它的通道接收到一些東西,所以它會阻塞直到發生——這永遠不會,因為沒有任何東西同時從它讀取。 因此你的僵局。

問題不在於函數的順序,也不在於通道緩沖——問題在於,如果您想同時運行兩個函數,那么首先調用的函數必須作為 g​​oroutine(或兩者)運行:

 go fibonacci(c, quit)
 pp(c,quit)    

會工作得很好,因為它同時調用fibonaccifibonacci ,然后調用可以同時運行的pp 你可以在這里看到它的實際效果: https : //play.golang.org/p/4o3T0z5n40X

如果您使用的是WaitGroup ,您甚至可以將它們作為 goroutine 運行,並且它們會同時運行:

 go fibonacci(c, quit, wg)
 go pp(c,quit, wg)    

盡管在您的情況下,這不是必需的,並且會增加復雜性。

通道make(chan int)隱式大小為零(參考: https : //golang.org/ref/spec#Making_slices_maps_and_channels

大小為零的通道是無緩沖的。 緩沖指定大小的通道make(chan int, n) 有關緩沖與非緩沖通道的討論,請參閱http://golang.org/ref/spec#Send_statements http://play.golang.org/p/VZAiN1V8-P 上的示例說明了差異。

這里, c := make(chan int)是無緩沖的。

如果改變這兩行的順序

 go pp(c,quit)    
 fibonacci(c, quit)

fibonacci(c,quit)
go pp(c,quit)

它會導致程序死鎖。 fibonacci函數中,查看select語句。

select {
    case c <- x:
        x, y = y, x+y
    case q:= <-quit:
        fmt.Println(q)
        return
}

select語句將保持阻塞狀態,直到其中一個case被填滿。 由於go pp(c,quit)fibonacci(c,quit)之后執行,所以沒有清除通道c或發送信號quit通道的過程。 這就是函數fibonacci(c,quit)將保持阻塞的原因。

如果您先調用 fibonnaci,它將在通道上發送值,但接收器尚未准備好。 這就是僵局背后的原因。

筆記:

默認情況下,發送和接收阻塞,直到對方准備好。 這允許 goroutine 在沒有顯式鎖或條件變量的情況下進行同步。

還是如果你想改變你的程序的順序,看看我們如何避免死鎖。

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case q:= <-quit:
            fmt.Println(q)
            return
        }
    }
}

func pp(c chan int, quit chan int){
   for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
}

func main() {
    c := make(chan int)
    quit := make(chan int)

    go func(){
       fibonacci(c, quit)
    }()
    pp(c,quit)
}

Go 操場上的工作代碼

在這種情況下,永遠記住等待 go 例程完成。 但是當您首先調用 fibonnaci 時,它已經發送了值,但接收器還沒有准備好,這會導致死鎖。

編輯:

因為即使你等待 go 例程完成。 它仍然會造成死鎖,因為通道不同步:

包主

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case q:= <-quit:
            fmt.Println(q)
            return
        }
    }
}

func pp(c chan int, quit chan int){
   defer wg.Done()
   for i := 0; i < 10; i++ {
            fmt.Println(<-c)
   }
   quit <- 0
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    fibonacci(c, quit)
    wg.Add(1)
    go pp(c,quit)  
    wg.Wait()
}

輸出:

致命錯誤:所有 goroutine 都處於睡眠狀態 - 死鎖!

goroutine 1 [選擇]: main.fibonacci(0x434080, 0x4340c0) /tmp/sandbox779301309/main.go:13 +0xc0 main.main() /tmp/sandbox779301309/main.go:34 +0x80

如果您更改代碼並在 for 循環的選擇中創建默認情況。 然后它將滿足這種情況並返回,而您的主要將退出。 永不結束循環讓它在退出情況下等待返回以使其返回。 這將起作用:

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case q, ok := <-quit:
            if ok {
                fmt.Println(q)
            }
            return
        default:
            fmt.Println("No value in any of the channel")
            return
        }
    }
}

func pp(c chan int, quit chan int) {
    for i := 0; i < 10; i++ {
        if value, ok := <-c; ok {
            fmt.Println(value)
        }
    }
    quit <- 0
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    fibonacci(c, quit)
    go pp(c, quit)
}

游樂場示例

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM