去通道死锁问题

时间:2018-11-01 05:23:07

标签: go deadlock channel

我刚刚开始学习golang,但还没有完全了解死锁是如何发生的。这是从golang游乐场教程改编而成的示例:

 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
}

为什么上面两行的顺序很重要?

3 个答案:

答案 0 :(得分:1)

您有两个功能,它们需要同时运行才能使通道通信正常工作-一个必须同时接收,另一个正在发送。在这种情况下:

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

您以goroutine的身份启动pp,它开始运行,然后调用fibonacci,以便两者都在运行,并且一切正常。如果您按照建议将其更改为:

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

然后,您将fibonacci作为常规函数而不是goroutine进行调用,这意味着直到fibonacci返回之前,下一行才执行。由于fibonacci期望从其通道接收到某些内容,因此它将阻塞直到发生这种情况-永远不会,因为没有任何内容同时从中读取。因此陷入僵局。

问题不是 functions 或通道缓冲的顺序-问题是,如果要同时运行两个函数,则首先调用的哪个函数必须作为goroutine运行(或两者兼有):

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

可以正常工作,因为它可以同时调用fibonacci,然后调用可以同时运行的pp。您可以在此处查看其运行情况:https://play.golang.org/p/4o3T0z5n40X

如果您使用的是WaitGroup,则甚至可以将它们作为goroutine来运行,并且它们可以同时运行:

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

尽管在您的情况下这不是必需的,但会增加复杂性。

答案 1 :(得分:0)

频道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)将保持阻塞的原因。

答案 2 :(得分:-1)

如果首先呼叫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 playground上的工作代码

始终记得在这种情况下等待执行例程。但是,当您首先致电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)
}

Playground Example