当我的简单 Go 程序 运行 时,为什么结果是死锁?
When my simple Go program run ,Why the result is deadlock?
这是我的全部 Go 代码!让我困惑的是 case balances <- balance:
没有 occurs.I 不知道为什么?
package main
import (
"fmt"
)
func main() {
done := make(chan int)
var balance int
balances := make(chan int)
balance = 1
go func() {
fmt.Println(<-balances)
done <- 1
}()
select {
case balances <- balance:
fmt.Println("done case")
default:
fmt.Println("default case")
}
<-done
}
default case
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/tmp/sandbox575832950/prog.go:29 +0x13d
goroutine 18 [chan receive]:
main.main.func1()
/tmp/sandbox575832950/prog.go:17 +0x38
created by main.main
/tmp/sandbox575832950/prog.go:16 +0x97
主 goroutine 在匿名 goroutine 函数执行从 balances
接收之前执行 select。主 goroutine 执行 select 中的默认子句,因为 balances
上没有就绪的接收器。主 goroutine 在 done
.
继续接收
goroutine 在从 balances
接收时阻塞,因为没有发送者。 Main 通过采用默认子句继续发送。
主 goroutine 在从 done
接收时阻塞,因为没有发送者。 goroutine 在从 balances
.
接收时被阻塞
通过将 select 语句替换为 balances <- balance
进行修复。 default
子句导致了问题。当删除 default
class 时,select 中剩余的所有内容都会发送到 balances
.
由于并发性,无法保证 goroutine 会在 select
之前执行。我们可以通过向 goroutine 添加打印来看到这一点。
go func() {
fmt.Println("Here")
fmt.Println(<-balances)
done <- 1
}()
$ go run test.go
default case
Here
fatal error: all goroutines are asleep - deadlock!
...
如果select先运行,balances <- balance
会阻塞; balances
没有缓冲区,也没有任何内容试图从中读取。 case balances <- balance
会阻塞,所以 select
跳过它并执行它的默认值。
然后 goroutine 运行并阻塞读取 balances
。同时,主代码块读取 done
。死锁。
您可以通过从 select 中删除默认情况并允许它阻塞直到准备好写入余额来解决此问题。
select {
case balances <- balance:
fmt.Println("done case")
}
或者您可以向 balances
添加一个缓冲区,以便在读取之前写入它。那么case balances <- balance
不阻塞
balances := make(chan int, 1)
What confused me is that case balances <- balance: did't occurs
具体来说:这是因为 select
有一个 default
案例。
每当您使用 go ...()
创建一个新的 goroutine 时,无法保证正在调用的 goroutine 或被调用的 goroutine 是否会 运行 接下来。
在实践中,调用 goroutine 中的下一个语句很可能接下来会执行 (there being no particularly good reason to stop it)。当然,我们应该编写始终正确运行的程序,而不仅仅是某些、大多数甚至几乎所有时间!使用 go ...()
的并发编程就是同步 goroutine,以便 必须 发生预期的行为。如果使用得当,渠道可以做到这一点。
I think the balances channel can receive data
它是一个无缓冲通道,因此它可以接收数据如果有人正在读取它。否则,对通道的写入将被阻塞。这让我们回到 select
.
既然你提供了一个default
案例,很可能调用go ...()
的goroutine会继续执行,而select
不能立即执行选择不同的大小写,会选择default
。因此,被调用的 goroutine 不太可能在主 goroutine 已经开始尝试写入它之前准备好从 balances
读取,失败并继续 default
的情况。
You can solve this by either removing the default case from the select and allowing it to block until balances is ready to be written to.
正如@Schwern 指出的那样,您当然可以。但请务必了解您不一定需要使用 select
才能使用频道。而不是只有一种情况的 select,you could instead just write
balances <- balance
fmt.Println("done")
select
在这种情况下不需要,default
对你不利,否则只有一种情况,所以不需要select
。您希望 主函数在该通道上阻塞。
you can add a buffer to balances so it can be written to before it is read from.
当然可以。但同样重要的是要理解,通道可能会阻止发送方和接收方,直到他们都准备好进行通信,这是通道的有效、有用和常见的用法。无缓冲通道不是问题的原因 - 为您的 select 提供 default
案例,从而导致意外行为的路径,才是问题的原因。
这是我的全部 Go 代码!让我困惑的是 case balances <- balance:
没有 occurs.I 不知道为什么?
package main
import (
"fmt"
)
func main() {
done := make(chan int)
var balance int
balances := make(chan int)
balance = 1
go func() {
fmt.Println(<-balances)
done <- 1
}()
select {
case balances <- balance:
fmt.Println("done case")
default:
fmt.Println("default case")
}
<-done
}
default case
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/tmp/sandbox575832950/prog.go:29 +0x13d
goroutine 18 [chan receive]:
main.main.func1()
/tmp/sandbox575832950/prog.go:17 +0x38
created by main.main
/tmp/sandbox575832950/prog.go:16 +0x97
主 goroutine 在匿名 goroutine 函数执行从 balances
接收之前执行 select。主 goroutine 执行 select 中的默认子句,因为 balances
上没有就绪的接收器。主 goroutine 在 done
.
goroutine 在从 balances
接收时阻塞,因为没有发送者。 Main 通过采用默认子句继续发送。
主 goroutine 在从 done
接收时阻塞,因为没有发送者。 goroutine 在从 balances
.
通过将 select 语句替换为 balances <- balance
进行修复。 default
子句导致了问题。当删除 default
class 时,select 中剩余的所有内容都会发送到 balances
.
由于并发性,无法保证 goroutine 会在 select
之前执行。我们可以通过向 goroutine 添加打印来看到这一点。
go func() {
fmt.Println("Here")
fmt.Println(<-balances)
done <- 1
}()
$ go run test.go
default case
Here
fatal error: all goroutines are asleep - deadlock!
...
如果select先运行,balances <- balance
会阻塞; balances
没有缓冲区,也没有任何内容试图从中读取。 case balances <- balance
会阻塞,所以 select
跳过它并执行它的默认值。
然后 goroutine 运行并阻塞读取 balances
。同时,主代码块读取 done
。死锁。
您可以通过从 select 中删除默认情况并允许它阻塞直到准备好写入余额来解决此问题。
select {
case balances <- balance:
fmt.Println("done case")
}
或者您可以向 balances
添加一个缓冲区,以便在读取之前写入它。那么case balances <- balance
不阻塞
balances := make(chan int, 1)
What confused me is that case balances <- balance: did't occurs
具体来说:这是因为 select
有一个 default
案例。
每当您使用 go ...()
创建一个新的 goroutine 时,无法保证正在调用的 goroutine 或被调用的 goroutine 是否会 运行 接下来。
在实践中,调用 goroutine 中的下一个语句很可能接下来会执行 (there being no particularly good reason to stop it)。当然,我们应该编写始终正确运行的程序,而不仅仅是某些、大多数甚至几乎所有时间!使用 go ...()
的并发编程就是同步 goroutine,以便 必须 发生预期的行为。如果使用得当,渠道可以做到这一点。
I think the balances channel can receive data
它是一个无缓冲通道,因此它可以接收数据如果有人正在读取它。否则,对通道的写入将被阻塞。这让我们回到 select
.
既然你提供了一个default
案例,很可能调用go ...()
的goroutine会继续执行,而select
不能立即执行选择不同的大小写,会选择default
。因此,被调用的 goroutine 不太可能在主 goroutine 已经开始尝试写入它之前准备好从 balances
读取,失败并继续 default
的情况。
You can solve this by either removing the default case from the select and allowing it to block until balances is ready to be written to.
正如@Schwern 指出的那样,您当然可以。但请务必了解您不一定需要使用 select
才能使用频道。而不是只有一种情况的 select,you could instead just write
balances <- balance
fmt.Println("done")
select
在这种情况下不需要,default
对你不利,否则只有一种情况,所以不需要select
。您希望 主函数在该通道上阻塞。
you can add a buffer to balances so it can be written to before it is read from.
当然可以。但同样重要的是要理解,通道可能会阻止发送方和接收方,直到他们都准备好进行通信,这是通道的有效、有用和常见的用法。无缓冲通道不是问题的原因 - 为您的 select 提供 default
案例,从而导致意外行为的路径,才是问题的原因。