去渠道和僵局

ith*_*uil 13 channel go

我正在努力理解Go语言.我试图创建两个goroutine,使用两个通道链接它们之间的流量:

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)
}
Run Code Online (Sandbox Code Playgroud)

正如所料,此代码打印:

 G1 got 1
 G2 got 1
 G1 got 1
 G2 got 1
 ....
Run Code Online (Sandbox Code Playgroud)

直到主要功能退出.

但是如果我从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)
}
Run Code Online (Sandbox Code Playgroud)

它输出

G1 got 1
G2 got 1
G1 got 1
G2 got 1
G1 got 2
Run Code Online (Sandbox Code Playgroud)

然后阻塞直到主要结束.

发送到c1的值"2"到达第一个goroutie,后者将其发送到c2,但第二个goroutine从未接收到.

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

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

Eva*_*haw 19

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

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

kill -6 [pid]
Run Code Online (Sandbox Code Playgroud)

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

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

gdb [executable name] [pid]
Run Code Online (Sandbox Code Playgroud)

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


nmi*_*els 18

创建的Go通道make(chan int)未缓冲.如果您想要一个缓冲的通道(不一定会阻塞),请使用make(chan int, 2)其中2是通道大小.

关于无缓冲通道的事情是它们也是同步的,所以它们总是阻塞写入和读取.

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

如果确实需要,您也可以仅使用同步通道来回避问题.

  • 当缓冲区填满时,缓冲通道会发生同样的事情吗? (3认同)