Go通道和死锁

Go通道和死锁,go,channel,Go,Channel,我正在努力理解围棋的语言。我试图创建两个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(

我正在努力理解围棋的语言。我试图创建两个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)
}
直到主函数退出

但如果我从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)
}
然后阻塞,直到主端

发送到c1的值“2”到达第一个goroutie,该goroutie将其发送到c2,但第二个 戈鲁廷从不接受

(在本例中,使用大小为1(c1或c2)的缓冲通道有效)


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

使用
make(chan int)
创建的Go通道不会被缓冲。如果需要缓冲通道(不一定会阻塞),请使用
make(chan int,2)
进行设置,其中2是通道的大小

无缓冲通道的一点是它们也是同步的,所以它们总是在读写时阻塞


它死锁的原因是您的第一个goroutine正在等待它的
c2nmichaels的答案是正确的,但是我想我要补充的是,在调试这样的问题时,有一些方法可以找出您在哪里死锁

一个简单的方法是,如果您使用的是类Unix操作系统,请运行以下命令

kill -6 [pid]
这将终止程序并为每个goroutine提供堆栈跟踪

一种稍微复杂一点的方法是连接gdb

gdb [executable name] [pid]

您可以正常检查活动goroutine的堆栈和变量,但据我所知,切换goroutine并不容易。您可以按常规方式切换操作系统线程,但这可能不足以提供帮助。

为了防止通道溢出,您可以请求通道的当前容量,并在再次写入之前将其干燥

在我的例子中,游戏以每秒60帧的速度进行,鼠标移动得更快,因此在再次写入之前检查频道是否已清除总是好的

请注意,以前的数据已丢失

package main

import (
    "fmt"
)

func main() {
    // you must specify the size of the channel, 
    // even for just one element, or the code doesn't work
    ch := make( chan int, 1 )
    fmt.Printf("len: %v\n", len(ch))
    fmt.Printf("cap: %v\n\n", cap(ch))

    ch <- 1

    for i := 0; i != 100; i += 1 {
        fmt.Printf("len: %v\n", len(ch))
        fmt.Printf("cap: %v\n\n", cap(ch))

        if cap( ch ) == 1 {
            <- ch
        }

        ch <- i

        fmt.Printf("len: %v\n", len(ch))
        fmt.Printf("cap: %v\n\n", cap(ch))
    }
    fmt.Printf("end!\n")
}
主程序包
进口(
“fmt”
)
func main(){
//必须指定通道的大小,
//即使只有一个元素,或者代码不起作用
ch:=制造(成交量,1)
fmt.Printf(“len:%v\n”,len(ch))
fmt.Printf(“上限:%v\n\n”,上限(ch))

当缓冲区填满时,同样的事情也会发生在缓冲通道上?@ithkuil:在这种情况下,所有通道的所有缓冲区都必须填满,所以你的示例不会导致它。如果你的程序总是在通道中输入的比输出的多,你最终会遇到问题。所以你只需要选择一个任意大的缓冲区缓冲区大小并希望它永远不会填满?似乎不太可靠。谢谢您的提示!有没有一种方法可以打印堆栈跟踪而不终止进程?也许,从进程内部调用一些运行时函数?是的,您可以导入运行时/调试并调用堆栈以获取字符串或打印堆栈以仅转储到stdout。我支持ose如果你的应用程序有一个单独的goroutine来处理来自os/signals的信号,你仍然可以使用它来调试死锁。感谢
kill-6
:找到通道阻塞位置的最简单方法!
gdb [executable name] [pid]
package main

import (
    "fmt"
)

func main() {
    // you must specify the size of the channel, 
    // even for just one element, or the code doesn't work
    ch := make( chan int, 1 )
    fmt.Printf("len: %v\n", len(ch))
    fmt.Printf("cap: %v\n\n", cap(ch))

    ch <- 1

    for i := 0; i != 100; i += 1 {
        fmt.Printf("len: %v\n", len(ch))
        fmt.Printf("cap: %v\n\n", cap(ch))

        if cap( ch ) == 1 {
            <- ch
        }

        ch <- i

        fmt.Printf("len: %v\n", len(ch))
        fmt.Printf("cap: %v\n\n", cap(ch))
    }
    fmt.Printf("end!\n")
}