GoLang教程中的死锁错误

时间:2014-12-15 00:33:03

标签: concurrency go

我正在尝试做这个教程 -

https://tour.golang.org/concurrency/8

这是我的代码

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
    if t != nil {
        Walk(t.Left, ch)
        ch <- t.Value
        Walk(t.Right,ch)
    }
}

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go Walk(t1, ch1)
    go Walk(t2, ch2)
    for i:= range ch1 {
        if i != <-ch2 {

            return false
        }
    }


    return true

}

func main() {
    isSame := Same(tree.New(1), tree.New(1))    
    if isSame {
        fmt.Println("SAME")
    } else {
        fmt.Println("DIFF")
    }
}

但是我收到了这个错误 -

fatal error: all goroutines are asleep - deadlock!

它运作了一次,然后我再次运行它并且它停止工作......或者我或者我疯了。

发生了什么?

1 个答案:

答案 0 :(得分:3)

问题是你永远不会关闭ch1,所以你的for i:= range ch1循环永远不会结束;它只是读取,直到通道中没有值,然后它阻塞。此时,将只有一个goroutine,并且它被阻止听空信道,所以Go将中止您看到的消息。 (同样地,你永远不会关闭ch2,但在你的情况下,这不会发生。如果ch2的值低于ch1,那么就会导致死锁。)

说实话,我不确定究竟是什么解决方案“Tour of Go”的人心目中。

一个可行但完全作弊的选项是硬编码你只会看到十个值的事实:

for i := 0; i < 10; i++ {
    if <-ch1 != <-ch2 {
        return false
    }
}

更好的选择是Same使用匿名函数调用Walk然后关闭频道:

go func() {
    Walk(t1, ch1)
    close(ch1)
}()

(或者您可以使用非匿名函数;将其称为walkAndClose或其他内容。)

顺便提一下,您的Same函数假定这两棵树的大小相同。如果t1包含更多元素,那么t2将在末尾隐式填充零(因为<-ch20关闭并耗尽时返回ch2,如果t1更短,那么t2将在结尾隐式截断。也许你是O.K.为了本练习的目的而做出这个假设,但是如果你希望Same总是为不同大小的树返回false,你可以将你的循环改为:

for i := range ch1 {
    j, receivedJ := <-ch2
    if i != j || ! receivedJ {
        return false
    }
}
_, receivedJ := <-ch2
if receivedJ {
    return false
}

(我使用<-ch2的双返回值形式来检测频道是否已关闭且已用尽;如果receivedJ true将为<-ch2 }从频道返回实际值,如果默认返回false,则返回0