【问题标题】:Reading outputs from two channels in a forever loop在永远循环中从两个通道读取输出
【发布时间】:2018-05-05 01:34:35
【问题描述】:

我正在做tour.golang 的树练习。我试图实现与下面编写的相同的功能。

func Same(t1, t2 *tree.Tree) bool {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go Walk(t1, ch1);
    go Walk(t2, ch2);

    for c := range ch1 {
        d := <- ch2
        if c-d !=0 {
            return false
        }   

    }
    return true
}

使用永远循环,我想比较ch1 的输出是否与ch2 的输出不同。但以下是抛出此错误:

致命错误:所有 goroutine 都处于休眠状态 - 死锁!

live version

【问题讨论】:

    标签: go


    【解决方案1】:

    您看到死锁的原因非常简单:您的范围超过了ch1,但从未关闭它,因此 for 循环永远不会终止。

    您可以通过手动迭代每棵树特定次数来解决此问题,例如 main() 中的 0..10 循环:

    // 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 := 0; i < 10; i++ {
            c := <-ch1
            d := <-ch2
            if c-d != 0 {
                return false
            }
    
        }
        return true
    }
    

    Playground

    或者,您可以更改 Walk 的签名以接受等待组参数,该参数由 Walk 的调用者递增,并在每个 Walk 返回时递减,并在您完成遍历后关闭通道:

    // Walk walks the tree t sending all values
    // from the tree to the channel ch.
    func Walk(t *tree.Tree, ch chan int, wg *sync.WaitGroup) {
        defer wg.Done()
        if t.Left != nil {
            wg.Add(1)
            Walk(t.Left, ch, wg)
        }
        ch <- t.Value
        if t.Right != nil {
            wg.Add(1)
            Walk(t.Right, ch, wg)
        }
    
    }
    
    // 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)
    
        var wg1 sync.WaitGroup
        wg1.Add(1)
        go Walk(t1, ch1, &wg1)
        go func() {
            wg1.Wait()
            close(ch1)
        }()
    
        var wg2 sync.WaitGroup
        wg2.Add(1)
        go Walk(t2, ch2, &wg2)
        go func() {
            // not strictly necessary, since we're not ranging over ch2, but here for completeness
            wg2.Wait()
            close(ch2)
        }()
    
        for c := range ch1 {
            d := <-ch2
            if c-d != 0 {
                return false
            }
    
        }
        return true
    }
    

    Playground

    【讨论】:

      【解决方案2】:

      您应该在遍历树后关闭通道以终止范围循环,以防树相等(注意:Same 在树的中缀遍历相等时返回 true,它们的结构不需要平等)。

      func WalkTreeAndThenCloseChannel(t *tree.Tree, ch chan int) {
          Walk(t, ch)
          close(ch)
      }
      
      func Same(t1, t2 *tree.Tree) bool {
          ch1 := make(chan int)
          ch2 := make(chan int)
          go WalkTreeAndThenCloseChannel(t1, ch1);
          go WalkTreeAndThenCloseChannel(t2, ch2);
      

      注意:您应该检查第二个通道是否已关闭,以防树有不同数量的项目并且没有发现差异(“线程饥饿”在这里比“死锁”更合适)。

      【讨论】:

        【解决方案3】:

        这里有一个问题,您没有在 walk 函数中向右子树的通道发送值。但是在另一边接收它,这就是死锁错误的原因。因为在从未发送的右子树的情况下,您会从通道接收一个值。

        【讨论】:

        • 注意,根据树结构不同,结果可能会有所不同!原始代码检查树的中缀遍历是否相同,而此代码检查后缀遍历。它们都比检查树是否实际上相同的限制更少。
        • 出错的原因是没有向另一端接收的通道发送值。如果条件应该满足,那么我们还必须向通道发送一个值。
        • 发送当前节点的值正确的节点遍历后违反了二叉树的有序语义。而且他的原始代码肯定是在右节点上调用Walk。
        • A) 不应该改变功能 B) 避免死锁只是因为后缀遍历不同,而中缀相同。调用 Same(x, x) 仍然会导致死锁,每当 Same 打算返回 true 时,仍然会发生死锁,因为通道已耗尽。
        • 是的,我会调查的
        猜你喜欢
        • 2018-05-20
        • 2019-11-06
        • 2012-10-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-24
        • 1970-01-01
        相关资源
        最近更新 更多