【问题标题】:Trouble with goroutines in a for loopfor循环中的goroutines问题
【发布时间】:2016-11-11 19:25:57
【问题描述】:

我正在尝试在Exercism 上解决这个问题:

编写一个程序,使用并行计算计算文本中字母的频率。

基本上,我有一个FreqMap 类型:

type FreqMap map[rune]int

还有一个Frequency 函数:

func Frequency(s string) FreqMap {
    m := make(FreqMap)
    for _, v := range s {
        m[v]++
    }
    return m
}

Exercism 提供了一个使用递归实现并发版本的示例,但我想使用for 循环实现我自己的版本。我想出了以下解决方案,但不起作用:

func ConcurrentFrequency(l []string) FreqMap {
    c := make(chan FreqMap)
    for i := 0; i < len(l); i++ {
        go func(i int) {
            c <- Frequency(l[i])
        }(i)
    }
    return <- c
}

这似乎只在 1 次迭代后返回,c 似乎只包含 1 个 goroutine 的结果;如果我添加 sync.WaitGroup,我会得到相同的结果。

你能解释一下我在这里缺少什么吗?

提前感谢您的帮助!

【问题讨论】:

    标签: for-loop go goroutine


    【解决方案1】:

    您的代码似乎只进行了一次迭代,因为ConcurrentFrequency 从通道返回第一个值,仅此而已。我想你想要这样的东西:

    func ConcurrentFrequency(l []string) chan FreqMap {
        c := make(chan FreqMap)
        go func() {
            var wg sync.WaitGroup
            wg.Add(len(l))
            for _, s := range l {
                go func(s string) {
                    defer wg.Done()
                    c <- Frequency(s)
                }(s)
            }
            wg.Wait()
            close(c)
        }()
        return c
    }
    

    现在它返回地图的通道,这些你可能想组合成一个地图:

    func main() {
        m := make(FreqMap)
        for v := range ConcurrentFrequency([]string{"foo", "bar","zoo"}) {
            for k, v := range v {
                m[k] += v
            }
        }
        fmt.Println(m)
    }
    

    更长的解释,不适合评论:

    for _, s := range l循环中,所有goroutines都写入同一个通道,但是由于该通道没有缓冲,一旦第一个值写入其中,它就是“满的”,这意味着不能写入其他值进去。所以循环中只有一个 goroutine 可以完成,wg.Done 只被调用一次。因此,如果源数组有多个字符串,其余的 gorutine 将无法完成,直到某些东西开始消耗通道中的值。但在您的版本中,它会卡在wg.Wait,因为并非所有goroutines 都已完成,因此ConcurrentFrequency 无法将通道返回给消费者。 以我编写ConcurrentFrequency 的方式,可以将通道返回给消费者,这(从通道读取)使其他Frequency(s) 调用能够写入通道。

    【讨论】:

    • 非常感谢,这似乎确实解决了我的问题!你能解释一下为什么我们需要第一个 goroutine 来包装 for 循环吗?如果我删除它,会有一个fatal error: all goroutines are asleep - deadlock!,但我看不出原因。再次感谢!
    • 不确定你是如何修改代码的,但我怀疑它在 wg.Wait 处死锁 - 届时将无法到达消费者循环,因为代码会卡在那里。我使用了 WaitGroup 以便在所有 Frequency 调用返回时关闭通道。这是您返回频道时的常见模式。
    • 那么如果没有一级goroutine闭包,wg.Wait()会超时吗?我不明白的是,forloop 中的 goroutine 闭包返回后,wg.Done() 就会减少 WaitGroup 计数器;那么为什么wg.Wait() 放在ConcurrentFrequency() 级别而不是嵌套goroutine 中时会出现死锁?很抱歉再次打扰您,只是想了解其中的大部分内容,非常感谢您的反馈!
    • 在答案末尾添加了解释,因为评论太长了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-22
    • 2011-08-01
    • 2011-06-10
    相关资源
    最近更新 更多