go - 一例 `all goroutines are asleep - deadlock!`我不知道为什么

标签 go deadlock goroutine

TL; DR:all goroutines are asleep, deadlock!的一种典型情况,但无法弄清

我正在解析Wiktionary XML转储以构建单词数据库。我将每篇文章的文本解析推迟到一个goroutine,希望它可以加快处理速度。
它是7GB,并且在串行方式下在我的计算机中处理时间不到2分钟,但是如果我可以利用所有内核,那为什么不行。

我是线程一般的新手,出现了all goroutines are asleep, deadlock!错误。
怎么了

这可能根本不起作用,因为它使用了无缓冲的 channel ,因此所有goroutine最终都有效地串行执行,但是我的想法是学习和理解线程并使用不同的替代方法测试它需要花费多长时间:

  • 无缓冲 channel
  • 不同大小的缓冲 channel
  • 一次仅调用与runtime.NumCPU()一样多的goroutines

  • 我的伪代码摘要:
    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

    最佳答案

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

  • 创建一个 channel (第39行,results := make(chan langs))和一个 WaitGroup (第40行,var wait sync.WaitGroup)。到目前为止,一切都很好。
  • 循环:在循环中,有时会剥离任务:
                if ...various conditions... {
                    wait.Add(1)
                    go parseTerm(results, &wait, text)
                }
    
  • 在循环中,有时从 channel 中进行非阻塞读取(如您的问题所示)。这里也没问题。但是...
  • 在循环的最后,使用:
    for res := range results {
        ...
    }
    

    在所有作者写完之后,再也不需要在一个地方调用close(results)了。此循环使用从 channel 读取的阻塞。只要某些编写程序goroutine仍在运行,阻塞读取就可以在不停止整个系统的情况下进行阻塞,但是当最后一个编写程序完成写入并退出时,将没有剩余的编写程序goroutine。其余的goroutine可能会救您,但没有。

  • 由于您正确使用了var wait(在正确的位置添加1,然后在编写器的正确位置调用Done()),因此解决方案是再添加一个goroutine,这将是一种抢救您的方法:
    go func() {
        wait.Wait()
        close(results)
    }()
    

    您应该在进入for res := range results循环之前就剥离此救援程序goroutine。 (如果您更早地将其分离,则可能会看到wait变量计数到零的时间太早,就在通过拆分另一个parseTerm将其重新计数之前)。

    这个匿名函数将阻塞wait变量的Wait()函数,直到最后一个编写程序goroutine调用了最终的wait.Done()为止,这将取消阻塞此goroutine。然后,此goroutine将调用close(results),它将安排for goroutine中的main循环完成,从而取消对该goroutine的阻塞。当此goroutine(救援人员)返回并因此终止时,不再有救援人员,但我们不再需要任何救援人员。

    (然后,此主代码会不必要地调用wait.Wait():由于直到新goroutine中的for已被解除阻止之前wait.Wait()才终止,所以我们知道下一个wait.Wait()将立即返回。因此,我们可以丢弃第二个调用,尽管将其留在无害)

    关于go - 一例 `all goroutines are asleep - deadlock!`我不知道为什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59432608/

    相关文章:

    go struct JSON decode 始终为空 return &{}

    mysql - 如何在不编写脚本的情况下在MySql中模拟死锁?

    bash - Go - 使用 goroutines 执行 Bash 命令 n 次并存储并打印其结果

    format - fmt.Sprintf 传递参数数组

    go - 这是死锁吗?为什么执行程序说是死锁?

    json - 为什么在接口(interface)中添加 func 会导致 json.Unmarshal 失败?

    go - 具有多个 channel 的多个 goroutine 的死锁

    python - 在Python中实现看门狗定时器的工具

    mysql - 即使我不使用事务,死锁也会影响我吗?

    http - goroutine 在请求时很快阻塞了 http 服务器