Go 通道和死锁

发布于 2024-11-07 01:38:03 字数 1150 浏览 0 评论 0原文

我正在尝试理解 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)
}

正如预期的那样,此代码打印:

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

直到主函数退出。

但是,如果我从主通道向其中一个通道发送另一个值,它会突然阻塞:

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,该 Goroutie 将其发送到 c2,但第二个 Goroutie Goroutine 从不接收。

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

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

I'm trying to understand the Go language. I tried to create two goroutines
that chain the flow between them using two channels:

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)
}

As expected this code prints:

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

Until the main function exits.

But if I send another value to one of the channels from main, it suddenly blocks:

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)
}

It outputs

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

and then blocks until the main ends.

The value "2" sent to c1 arrives to the first goroutie, which sends it to c2, but the second
goroutine never receives.

(Using buffered channels with size 1 (either c1 or c2) works in this example)

Why does it happen? When this happens in real code, how can I debug it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

芸娘子的小脾气 2024-11-14 01:38:03

nmichaels 的回答是正确的,但我想我应该补充一点,在调试这样的问题时,有一些方法可以找出陷入僵局的地方。

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

kill -6 [pid]

,这将终止程序并给出每个 goroutine 的堆栈跟踪。

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

gdb [executable name] [pid]

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

nmichaels is right on with his answer, but I thought I'd add that there are ways to figure out where you're deadlocking when debugging a problem like this.

A simple one is if you're on a Unix-like OS, run the command

kill -6 [pid]

This will kill the program and give a stack trace for each goroutine.

A slightly more involved way is to attach gdb.

gdb [executable name] [pid]

You can examine the stack and variables of the active goroutine as normal, but there's no easy way to switch goroutines that I know of. You can switch OS threads in the usual way, but that might not be enough to help.

请爱~陌生人 2024-11-14 01:38:03

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

无缓冲通道的特点是它们也是同步的,因此它们在写入和读取时总是会阻塞。

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

您还可以通过仅在确实需要时使用同步通道来回避问题。

Go channels created with make(chan int) are not buffered. If you want a buffered channel (that won't necessarily block), make it with make(chan int, 2) where 2 is the size of the channel.

The thing about unbuffered channels is that they are also synchronous, so they always block on write as well as read.

The reason it deadlocks is that your first goroutine is waiting for its c2 <- i to finish while the second one is waiting for c1 <- i to finish, because there was an extra thing in c1. The best way I've found to debug this sort of thing when it happens in real code is to look at what goroutines are blocked and think hard.

You can also sidestep the problem by only using synchronous channels if they're really needed.

七婞 2024-11-14 01:38:03

为防止通道溢出,可以询问通道当前容量,晾干后再进行写入。

就我而言,游戏以 60 fps 进行,鼠标移动速度更快,因此在再次写入之前检查通道是否已清除总是好的。

注意之前的数据丢失了

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")
}

to prevent the channel from overflowing, you can ask for the channel's current capacity and dry it before writing again.

in my case, the game takes place at 60fps and the mouse moves much faster, so it is always good to check that the channel has been cleared before writing again.

notice that the previous data is lost

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")
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文