我正在尝试理解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)在此示例中起作用)
为什么会这样?如果在实际代码中发生这种情况,我该如何调试呢?
答案 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。您可以通过常规方式切换操作系统线程,但这可能不足以提供帮助。