首页 > 解决方案 > 一个'所有goroutines都睡着了-死锁!'的例子我不知道为什么

问题描述

TL;DR:一个典型的all goroutines are asleep, deadlock!但无法弄清楚的案例

我正在解析维基词典 XML 转储以构建单词数据库。我将每篇文章的文本解析推迟到一个 goroutine 中,希望它能加快这个过程。
它是 7GB,在我的机器上连续处理时不到 2 分钟,但如果我可以利用所有内核,为什么不呢。

一般来说,我是线程新手,我遇到了一个all goroutines are asleep, deadlock!错误。
这里有什么问题?

这可能根本没有性能,因为它使用无缓冲的通道,所以所有的 goroutine 最终都会有效地串行执行,但我的想法是学习和理解线程,并用不同的替代方案来衡量它需要多长时间:

我的伪代码代码摘要:

while tag := xml.getNextTag() {
    wg.Add(1)
    go parseTagText(chan, wg, tag.text)

    // consume a channel message if available
    select {
    case msg := <-chan:
        // do something with msg            
    default:
    }
}
// reading tags finished, wait for running goroutines, consume what's left on the channel
for msg := range chan {
    // do something with msg
}
// Sometimes this point is never reached, I get a deadlock
wg.Wait()

----

func parseTagText(chan, wg, tag.text) {
    defer wg.Done()
    // parse tag.text
    chan <- whatever // just inform that the text has been parsed
}

完整代码: https:
//play.golang.org/p/0t2EqptJBXE

标签: godeadlockgoroutine

解决方案


在 Go Playground 上的完整示例中,您:

  • 创建一个通道(第 39 行results := make(chan langs))和一个等待组(第 40 行var wait sync.WaitGroup)。到目前为止,一切都很好。

  • 循环:在循环中,有时会衍生出一个任务:

                if ...various conditions... {
                    wait.Add(1)
                    go parseTerm(results, &wait, text)
                }
    
  • 在循环中,有时会从通道进行非阻塞读取(如您的问题所示)。这里也没有问题。但...

  • 在循环结束时,使用:

    for res := range results {
        ...
    }
    

    在所有作家完成之后,从来没有close(results)在一个地方打电话。此循环使用来自通道的阻塞读取。只要某个 writer goroutine 仍在运行,阻塞读取就可以在不让整个系统停止的情况下阻塞,但是当最后一个 writer 完成写入并退出时,就没有剩余的 writer goroutines。任何其他剩余的 goroutine 可能会拯救你,但没有。

由于您使用var wait正确(在正确的位置添加 1,并在编写Done()器的正确位置调用),解决方案是再添加一个 goroutine,这将是拯救您的那个:

go func() {
    wait.Wait()
    close(results)
}()

for res := range results你应该在进入循环之前分离这个救援者协程。(如果您更早地将其分拆,它可能会看到wait变量过早地倒数到零,就在它通过分拆另一个 重新计数之前parseTerm。)

这个匿名函数将阻塞在wait变量的Wait()函数中,直到最后一个编写者 goroutine 调用了 final wait.Done(),这将解除阻塞这个goroutine。然后这个 goroutine 将调用close(results),这将安排for你的 goroutine 中的循环main完成,解除阻塞该 goroutine。当这个 goroutine(救援者)返回并因此终止时,不再有救援者,但我们不再需要任何救援者。

wait.Wait()(然后这个主代码不必要地调用:因为for直到wait.Wait()的 goroutine中的已经解除阻塞才终止,我们知道这个 next将立即返回。所以我们可以放弃第二个调用,尽管把它留在里面是无害的。)wait.Wait()


推荐阅读