去渠道和僵局

时间:2011-05-12 19:40:28

标签: go channel

我正在尝试理解Go语言。我试图创建两个goroutines 用两个通道链接它们之间的流动:

func main() {
c1 := make(chan int)
c2 := make(chan int)

go func() {
    for i := range c1{
        println("G1 got", i)
        c2 <- i
    }
}()

go func() {
    for i := range c2 {
        println("G2 got", i)
        c1 <- i
    }
}()


c1 <- 1

time.Sleep(1000000000 * 50)
}

正如预期的那样,这段代码打印出来:

 G1 got 1
 G2 got 1
 G1 got 1
 G2 got 1
 ....

直到主要功能退出。

但是如果我向main中的一个频道发送另一个值,它会突然阻塞:

func main() {
c1 := make(chan int)
c2 := make(chan int)

go func() {
    for i := range c1{
        println("G1 got", i)
        c2 <- i
    }
}()

go func() {
    for i := range c2 {
        println("G2 got", i)
        c1 <- i
    }
}()


c1 <- 1

time.Sleep(1000000000 * 1)

c1 <- 2

time.Sleep(1000000000 * 50)
}

输出

G1 got 1
G2 got 1
G1 got 1
G2 got 1
G1 got 2

然后阻止直到主要结束。

发送到c1的值“2”到达第一个goroutie,将其发送到c2,但是第二个goroutie goroutine永远不会收到。

(使用大小为1的缓冲通道(c1或c2)在此示例中起作用)

为什么会这样?如果在实际代码中发生这种情况,我该如何调试呢?

2 个答案:

答案 0 :(得分:18)

使用make(chan int)创建的Go频道不会被缓冲。如果你想要一个缓冲的频道(不一定会阻止),请使用make(chan int, 2),其中2是频道的大小。

关于无缓冲通道的事情是它们也是同步的,因此它们总是阻止以及读取。

它死锁的原因是你的第一个goroutine正在等待c2 <- i完成,而第二个goroutine等待c1 <- i完成,因为c1中有一个额外的东西。我发现在实际代码中调试此类事情的最佳方法是查看哪些goroutine被阻止并认真考虑。

如果确实需要同步频道,您也可以回避问题。

答案 1 :(得分:18)

nmichaels对他的回答是正确的,但我想我会补充说,在调试这样的问题时,有办法找出你在哪里陷入僵局。

一个简单的问题是,如果您使用的是类Unix操作系统,请运行命令

kill -6 [pid]

这将终止程序并为每个goroutine提供堆栈跟踪。

稍微复杂一点的方法是附加gdb。

gdb [executable name] [pid]

您可以正常检查活动goroutine的堆栈和变量,但是没有简单的方法来切换我所知道的goroutine。您可以通过常规方式切换操作系统线程,但这可能不足以提供帮助。