【问题标题】:Go all goroutines are asleep deadlockGo 所有的 goroutines 都睡着了死锁
【发布时间】:2019-08-08 09:08:39
【问题描述】:

似乎无法弄清楚为什么我会收到错误消息:致命错误:所有 goroutines 都处于睡眠状态 - 死锁!。

我怀疑我下面的块中发生了竞争条件,该条件只能在通道关闭后执行。

我认为添加一个同步 WaitGroup 会有所帮助,但它只是给了我这个死锁。我看到的和我在网上看到的样本很接近,所以我不确定这里有什么问题。

func S3UploadFolder(instance *confighelper.Instance, sess *session.Session, 
    srcFolder string, bucketName string) (err error) {

    log.Println("S3UploadFolder", srcFolder, bucketName)

    wg := &sync.WaitGroup{}

    // find files recursively
    walker := make(fileWalk)
    wg.Add(1)

    go func() {

        // Gather the files to upload by walking the path recursively
        if err := filepath.Walk(srcFolder, walker.Walk); err != nil {
            log.Fatalln("Walk failed:", err)
        }
        wg.Done()
        close(walker)

    }()
    wg.Wait()

    for path := range walker {
    // THE GO routine above needs to have finished by the time this for loop 
       // ranges over the channel
         fmt.Println(path)

 }



return
}


type fileWalk chan string

func (f fileWalk) Walk(path string, info os.FileInfo, err error) error {
    if err != nil {
        return err
    }
    if !info.IsDir() {
        f <- path
    }
    return nil
}

【问题讨论】:

  • 主 goroutine 在调用 wg.Wait() 之前没有在通道上接收。 walker goroutine 阻塞发送到通道,因为没有接收器。您没有显示所有相关代码,但修复可能是删除与等待组相关的所有代码。
  • 抱歉,是的, 部分包含通道上的范围。我将更新我的问题以包含此代码。我试图等到 go 例程完成后再继续块
  • 为什么 walker goroutine 需要在遍历通道的循环之前完成?
  • 我正在解决一些文件未包含在 for 范围内的缺陷。我怀疑在遇到 for range 语句时 go 线程仍在查找文件的竞争条件。我的想法是此时通道尚未完成,因此测距将只是在 walker 中找到的总文件的一个子集。这就是我设置同步等待的原因。
  • 在通道关闭之前,通道上的范围不会完成。 walker goroutine 在 walk 完成之前不会关闭通道。通道操作不会丢失文件名。

标签: go


【解决方案1】:

walker 频道没有缓冲。在发送方和接收方准备好之前,无缓冲通道上的通信不会继续进行。

死锁是这样的:主 goroutine 通过调用 wg.Done() 等待 walker goroutine 完成。 walker goroutine 等待主 goroutine 在通道上接收。

通过删除与等待组相关的所有代码来修复程序。不需要等待组。在 walker goroutine 关闭通道之前,主 goroutine 中的通道上的范围不会完成。 walker goroutine 在 walk 完成之前不会关闭通道。无需其他协调。

您还可以通过删除 goroutine 和通道来修复代码:

err := filepath.Walk(srcFolder, func(path string, info os.FileInfo, err error) error {
    if err != nil {
        return err
    }
    if info.IsDir() {
        return nil
    }

    // Insert body of for path := range walker here ... 
    fmt.Println(path)

    return nil
})
if err != nil {
    log.Fatal(err)
}

另一种选择是创建一个容量大于要遍历的文件数量的缓冲通道,但这需要提前知道文件的数量,并且与在切片中收集文件名相比没有任何好处。

【讨论】:

    【解决方案2】:

    如所写(和所示),您绝对不得在运行for path := range walker 循环之前调用wg.Wait()。当循环终止时,您可以(但不需要)调用wg.Wait()。您根本不需要 wg 变量。

    你的评论说:

    // THE GO routine above needs to have finished by the time this for loop 
    // ranges over the channel
    

    但是在for 循环中没有任何东西需要完成函数,并且有一些东西——这里的整体策略——要求goroutine 被阻塞在中发送,因为for 循环只会在发送者(即 goroutine)关闭通道时完成。

    (请参阅Cerise Limón's answer 了解 goroutine 在发送中被阻塞的原因。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-16
      • 2012-01-06
      • 1970-01-01
      • 2016-04-06
      • 2023-02-01
      • 1970-01-01
      • 2016-07-30
      • 1970-01-01
      相关资源
      最近更新 更多