英文:
Go channels and deadlock
问题
我正在尝试理解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
....
直到main函数退出。
但是,如果我从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
然后阻塞,直到main函数结束。
值"2"被发送到c1并到达第一个goroutine,它将其发送到c2,但第二个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?
答案1
得分: 21
nmichaels的回答是正确的,但我想补充一下,在调试这类问题时,有办法找出死锁的位置。
一个简单的方法是,如果你在类Unix的操作系统上,运行以下命令:
kill -6 [pid]
这将终止程序并为每个goroutine提供堆栈跟踪。
一个稍微复杂一点的方法是附加gdb。
gdb [可执行文件名] [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.
答案2
得分: 20
使用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.
答案3
得分: 2
为了防止通道溢出,您可以在再次写入之前询问通道的当前容量并将其清空。
在我的情况下,游戏以60fps进行,鼠标移动速度很快,因此在再次写入之前检查通道是否已清空总是很好的选择。
请注意,先前的数据将会丢失。
package main
import (
"fmt"
)
func main() {
// 您必须指定通道的大小,即使只有一个元素,否则代码将无法正常工作
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")
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论