繁体   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