我正在尝试通过同时将计算分为100组来计算阶乘的挑战,我解决了WaitGroups上的许多问题,但是仍然在calculateFactorial函数中, channel 范围上出现了死锁。
希望有人可以在这里指出问题,谢谢。

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    in := make (chan int)
    out := make (chan float64)



    out = calculateFactorial(genConcurrentGroup(in, &wg), &wg)

    go func() {
        in <- 10
        close(in)
    }()

    fmt.Println(<-out)

    wg.Wait()


}

//split input number into groups
//the result should be a map of [start number, number in group]
//this is not heavy task so run in one go routine
func genConcurrentGroup(c chan int, wg *sync.WaitGroup) chan map[int]int{
    out := make(chan map[int]int)

    go func() {
        //100 groups
        total:= <- c
        wg.Done()
        //element number in group
        elemNumber := total / 100
        extra := total % 100
        result := make(map[int]int)
        if elemNumber>0{
            //certain 100 groups
            for i:=1 ;i<=99;i++{
                result[(i-1) * elemNumber + 1] = elemNumber
            }
            result[100] = extra + elemNumber
        }else{
            //less than 100
            for i:=1;i<=total;i++{
                result[i] = 1
            }
        }

        out <- result
        close(out)
    }()
    return out
}

//takes in all numbers to calculate multiply result
//this could be heavy so can do it 100 groups together
func calculateFactorial(nums chan map[int]int, wg *sync.WaitGroup) chan float64{
    out := make(chan float64)


    go func() {
        total:= <- nums
        wg.Done()
        fmt.Println(total)

        oneResult := make(chan float64)

        var wg2 sync.WaitGroup
        wg2.Add(len(total))

        for k,v := range total{
            fmt.Printf("%d %d \n",k,v)
            go func(k int, v int) {
                t := 1.0
                for i:=0;i<v;i++{
                    t = t * (float64(k) + float64(i))
                }
                fmt.Println(t)
                oneResult <- t
                wg2.Done()
            }(k,v)
        }

        wg2.Wait()
        close(oneResult)

        result := 1.0
        for n := range oneResult{  //DEADLOCK HERE! Why?
            result *= n
        }


        fmt.Printf("Result: %f\n",result)

        out <- result

    }()
    return out
}

更新:

感谢JesséCatrinck的回答,通过简单地将oneResult更改为缓冲 channel ,可以解决上述代码中的问题。但是在https://stackoverflow.com/a/15144455/921082中有一个报价



所以有人可以帮我弄清楚如何不使用缓冲 channel 吗?是否有可能?

此外,我对造成死锁的原因进行了一些研究。

https://stackoverflow.com/a/18660709/921082这样的报价,



因此,在我的原始情况下,可能导致死锁的原因可能是:
  • channel 范围未收到?
  • 在单独的go例程上未接收到 channel 范围。 ?
  • oneResult未正确关闭,因此跨 channel 范围不知道终点在哪里?

  • 对于数字3,我不知道在范围结束之前关闭oneResult是否有任何错误,因为此模式出现在互联网上的许多示例中。如果它是3,那在 WaitGroup 中可能有问题吗?

    我在另一篇文章https://robertbasic.com/blog/buffered-vs-unbuffered-channels-in-golang/的情况非常相似,
    第二课,他使用for { select {} }无限循环作为范围的替代,似乎解决了他的问题。
     go func() {
            for{
                select {
                case p := <-pch:
                    findcp(p)
                }
            }
        }()
    



    谢谢

    最佳答案

    死锁不在跨 channel 范围循环中。如果在playground上运行代码,则会在堆栈跟踪的顶部看到该错误是由wg2.Wait引起的(操场上的第88行,由堆​​栈跟踪指向)。同样在stacktrace中,您可以看到由于死锁而尚未完成的所有goroutine,这是因为oneResult<-t从未完成,因此循环中启动的所有goroutine都没有完成。

    所以主要问题在这里:

    wg2.Wait()
    close(oneResult)
    
    // ...
    
    for n := range oneResult{
    // ...
    

    我想,也不希望在封闭的 channel 上循环。但是,即使您没有关闭 channel ,该循环也永远不会开始,因为wg2.Wait()将等待直到完成。
    oneResult <- t
    wg2.Done()
    

    但是永远不会做,因为它依赖于已经在运行的循环。除非另一边有人从该 channel 接收该行,否则oneResult <- t行将不会完成,这就是您的循环,但是,整个 channel 范围循环仍在等待wg2.Wait()完成。

    因此,实质上,您在 channel 的发送方和接收方之间具有“循环依赖”。

    要解决此问题,您需要允许循环开始从 channel 接收,同时仍要确保完成后关闭 channel 。您可以通过将两条等待关闭行包装到它们自己的goroutine中来进行操作。

    https://play.golang.com/p/rwwCFVszZ6Q

    关于go - 具有多个 WaitGroup 的管道中 channel 范围内的死锁,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/54887502/

    10-12 00:36
    查看更多