为了账号安全,请及时绑定邮箱和手机立即绑定

“所有 goroutine 都在睡觉 - 死锁!”的情况我不明白为什么

“所有 goroutine 都在睡觉 - 死锁!”的情况我不明白为什么

Go
慕的地10843 2023-08-14 16:21:35
一个典型的案例all goroutines are asleep, deadlock!,但无法弄清楚我正在解析维基词典 XML 转储来构建单词数据库。我将每篇文章的文本的解析推迟到一个 Goroutine,希望它能加快这个过程。它有 7GB,在我的机器上串行执行时,处理时间不到 2 分钟,但如果我可以利用所有内核,为什么不呢。一般来说,我是线程新手,遇到错误all goroutines are asleep, deadlock!。这是怎么回事?这可能根本没有性能,因为它使用无缓冲的通道,因此所有 goroutine 实际上最终都会串行执行,但我的想法是学习和理解线程,并衡量不同替代方案所需的时间:无缓冲通道不同大小的缓冲通道一次只调用尽可能多的 goroutineruntime.NumCPU()我的伪代码代码摘要:
查看完整描述

3 回答

?
三国纷争

TA贡献1804条经验 获得超7个赞

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


查看完整回答
反对 回复 2023-08-14
?
慕莱坞森

TA贡献1810条经验 获得超4个赞

在 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 goroutine 了。任何其他剩余的 goroutine 可能会拯救你,但没有。

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

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

您应该在进入循环之前关闭这个救援 goroutine for res := range results。(如果您更早地将其分离,它可能会wait很快看到变量计数减至零,就在它通过分离另一个 再次计数之前parseTerm。)

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

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


查看完整回答
反对 回复 2023-08-14
?
慕仙森

TA贡献1827条经验 获得超7个赞

问题是没有任何东西可以关闭结果通道,但范围循环仅在关闭时退出。我简化了您的代码来说明这一点并提出了一个解决方案 - 基本上使用 goroutine 中的数据:


// This is our producer

func foo(i int, ch chan int, wg *sync.WaitGroup) {

    defer wg.Done()

    ch <- i

    fmt.Println(i, "done")

}

// This is our consumer - it uses a different WG to signal it's done

func consumeData(ch chan int, wg *sync.WaitGroup) {

    defer wg.Done()

    for x := range ch {

        fmt.Println(x)

    }

    fmt.Println("ALL DONE")

}


func main() {

    ch := make(chan int)

    wg := sync.WaitGroup{}

    // create the producers

    for i := 0; i < 10; i++ {

        wg.Add(1)

        go foo(i, ch, &wg)

    }

    // create the consumer on a different goroutine, and sync using another WG

    consumeWg := sync.WaitGroup{}

    consumeWg.Add(1)

    go consumeData(ch,&consumeWg)


    wg.Wait()  // <<<< means that the producers are done

    close(ch) // << Signal the consumer to exit

    consumeWg.Wait() // << Wait for the consumer to exit

}


查看完整回答
反对 回复 2023-08-14
  • 3 回答
  • 0 关注
  • 147 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信