我刚刚开始学习golang,但还没有完全了解死锁是如何发生的。这是从golang游乐场教程改编而成的示例:
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case q:= <-quit:
fmt.Println(q)
return
}
}
}
func pp(c chan int, quit chan int){
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}
func main() {
c := make(chan int)
quit := make(chan int)
// here it's good, no deadlock
go pp(c,quit)
fibonacci(c, quit)
// but if change the order of above two line:
// fibonacci(c,quit)
// go pp(c,quit)
// it will deadlock
}
为什么上面两行的顺序很重要?
答案 0 :(得分:1)
您有两个功能,它们需要同时运行才能使通道通信正常工作-一个必须同时接收,另一个正在发送。在这种情况下:
go pp(c,quit)
fibonacci(c, quit)
您以goroutine的身份启动pp
,它开始运行,然后调用fibonacci
,以便两者都在运行,并且一切正常。如果您按照建议将其更改为:
fibonacci(c, quit)
go pp(c,quit)
然后,您将fibonacci
作为常规函数而不是goroutine进行调用,这意味着直到fibonacci
返回之前,下一行才执行。由于fibonacci
期望从其通道接收到某些内容,因此它将阻塞直到发生这种情况-永远不会,因为没有任何内容同时从中读取。因此陷入僵局。
问题不是 functions 或通道缓冲的顺序-问题是,如果要同时运行两个函数,则首先调用的哪个函数必须作为goroutine运行(或两者兼有):
go fibonacci(c, quit)
pp(c,quit)
可以正常工作,因为它可以同时调用fibonacci
,然后调用可以同时运行的pp
。您可以在此处查看其运行情况:https://play.golang.org/p/4o3T0z5n40X
如果您使用的是WaitGroup
,则甚至可以将它们作为goroutine来运行,并且它们可以同时运行:
go fibonacci(c, quit, wg)
go pp(c,quit, wg)
尽管在您的情况下这不是必需的,但会增加复杂性。
答案 1 :(得分:0)
频道make(chan int)
的隐式大小为零(参考:https://golang.org/ref/spec#Making_slices_maps_and_channels)
大小为零的通道未缓冲。指定大小为make(chan int, n)
的通道被缓冲。看到
http://golang.org/ref/spec#Send_statements讨论缓冲与非缓冲
渠道。 http://play.golang.org/p/VZAiN1V8-P的示例说明了差异。
此处c := make(chan int)
是未缓冲的。
如果更改这两行的顺序
go pp(c,quit)
fibonacci(c, quit)
收件人
fibonacci(c,quit)
go pp(c,quit)
这将导致程序死锁。在fibonacci
函数中,查看select
语句。
select {
case c <- x:
x, y = y, x+y
case q:= <-quit:
fmt.Println(q)
return
}
select
语句将一直处于阻塞状态,直到case
之一被填满。由于go pp(c,quit)
在fibonacci(c,quit)
之后执行,因此没有清除通道c
或向quit
通道发送信号的过程。这就是为什么函数fibonacci(c,quit)
将保持阻塞的原因。
答案 2 :(得分:-1)
如果首先呼叫fibonnaci,它将在通道上发送该值,但接收器尚未准备好。这就是造成僵局的原因。
注意:
默认情况下,发送和接收块,直到另一侧准备好为止。 这允许goroutine进行同步而无需显式锁定或 条件变量。
还是要更改程序的顺序以了解如何避免死锁。
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case q:= <-quit:
fmt.Println(q)
return
}
}
}
func pp(c chan int, quit chan int){
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}
func main() {
c := make(chan int)
quit := make(chan int)
go func(){
fibonacci(c, quit)
}()
pp(c,quit)
}
Go playground上的工作代码
始终记得在这种情况下等待执行例程。但是,当您首先致电fibonnaci时,它已经发送了值,但接收器尚未准备好,这导致了死锁。
编辑:
因为即使您等待go例程完成,也是如此。仍然会产生死锁,因为通道未按以下方式同步:
软件包主要
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case q:= <-quit:
fmt.Println(q)
return
}
}
}
func pp(c chan int, quit chan int){
defer wg.Done()
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}
func main() {
c := make(chan int)
quit := make(chan int)
fibonacci(c, quit)
wg.Add(1)
go pp(c,quit)
wg.Wait()
}
输出:
致命错误:所有goroutine都在睡觉-死锁!
goroutine 1 [选择]:main.fibonacci(0x434080,0x4340c0) /tmp/sandbox779301309/main.go:13 + 0xc0 main.main() /tmp/sandbox779301309/main.go:34 + 0x80
如果更改代码并在for循环选择中创建默认大小写。然后它将满足这种情况,然后在您的主退出时返回。永无止境的循环让它在退出情况下等待返回。这将起作用:
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case q, ok := <-quit:
if ok {
fmt.Println(q)
}
return
default:
fmt.Println("No value in any of the channel")
return
}
}
}
func pp(c chan int, quit chan int) {
for i := 0; i < 10; i++ {
if value, ok := <-c; ok {
fmt.Println(value)
}
}
quit <- 0
}
func main() {
c := make(chan int)
quit := make(chan int)
fibonacci(c, quit)
go pp(c, quit)
}