[英]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
期望從它的通道接收到一些東西,所以它會阻塞直到發生——這永遠不會,因為沒有任何東西同時從它讀取。 因此你的僵局。
問題不在於函數的順序,也不在於通道緩沖——問題在於,如果您想同時運行兩個函數,那么首先調用的函數必須作為 goroutine(或兩者)運行:
go fibonacci(c, quit)
pp(c,quit)
會工作得很好,因為它同時調用fibonacci
那fibonacci
,然后調用可以同時運行的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.