golang中的死锁错误

Deadlock error in golang

最近在看go,迷上了,看起来好有趣!完成本教程后,我想自己构建一些东西:我想列出我音乐库中的所有歌曲。我觉得我可以在这里从 go 的并发中获益。当例行程序沿着目录树向下走时,它将音乐文件(这些文件的路径)推入一个通道,然后由另一个读取 ID3 标签的例程拾取,所以我不必等到每个文件都被找到.

这是我简单而天真的方法:

package main

import (
    "fmt"
    "os"
    "path/filepath"
    "strings"
    "sync"
)

const searchPath = "/Users/luma/Music/test" // 5GB of music.

func main() {
    files := make(chan string)

    var wg sync.WaitGroup
    wg.Add(2)

    go printHashes(files, &wg)
    go searchFiles(searchPath, files, &wg)

    wg.Wait()
}

func searchFiles(searchPath string, files chan<- string, wg *sync.WaitGroup) {
    visit := func(path string, f os.FileInfo, err error) error {
        if !f.IsDir() && strings.Contains(".mp4.mp3.flac", filepath.Ext(f.Name())) {
            files <- path
        }
        return err
    }

    if err := filepath.Walk(searchPath, visit); err != nil {
        fmt.Println(err)
    }

    wg.Done()
}

func printHashes(files <-chan string, wg *sync.WaitGroup) {
    for range files {
        fmt.Println(<-files)
    }

    wg.Done()
}

这个程序还没有读取标签。相反,它只是打印文件路径。这有效,它以极快的速度列出所有音乐文件!但是我在程序完成后看到这个错误:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc42007205c)
    /usr/local/Cellar/go/1.7.4_2/libexec/src/runtime/sema.go:47 +0x30
sync.(*WaitGroup).Wait(0xc420072050)
    /usr/local/Cellar/go/1.7.4_2/libexec/src/sync/waitgroup.go:131 +0x97
main.main()
    /Users/luma/Code/Go/src/github.com/LuMa/test/main.go:22 +0xfa

goroutine 17 [chan receive]:
main.printHashes(0xc42008e000, 0xc420072050)
    /Users/luma/Code/Go/src/github.com/LuMa/test/main.go:42 +0xb4
created by main.main
    /Users/luma/Code/Go/src/github.com/LuMa/test/main.go:19 +0xab
exit status 2

是什么导致了死锁?

因为您需要关闭 files 频道。 在你的情况下,你没有关闭它,所以

for range files { fmt.Println(<-files) } 将等待从 files 通道获取值。所以 wg.Done() 永远不会在 printHashes 中完成。

func searchFiles(searchPath string, files chan<- string, wg *sync.WaitGroup) {
    visit := func(path string, f os.FileInfo, err error) error {
        if !f.IsDir() && strings.Contains(".mp4.mp3.flac", filepath.Ext(f.Name())) {
            files <- path
        }
        return err
    }

    if err := filepath.Walk(searchPath, visit); err != nil {
        fmt.Println(err)
    }

    wg.Done()
    close(files) // close the chanel, because you don't put thing into the channel anymore.
}

searchFiles 内,您想 close(files) 发送完毕。此约定称为 sender-closes(接收器永不关闭)。另外,删除对 wg.Done() 的调用,因为您还没有完成...频道上可能还有项目。

close(files) 将向 for range files 发出信号以关闭并退出循环,这将调用您的 wg.Done() 向主函数发出一切已完成的信号。

(未在手机上测试)

package main

import (
    "fmt"
    "os"
    "path/filepath"
    "strings"
    "sync"
)

const searchPath = "/Users/luma/Music/test" // 5GB of music.

func main() {
    files := make(chan string)

    var wg sync.WaitGroup
    wg.Add(1)

    go printHashes(files)
    go searchFiles(searchPath, files, &wg)

    wg.Wait()
}

func searchFiles(searchPath string, files chan<- string) {
    visit := func(path string, f os.FileInfo, err error) error {
        if !f.IsDir() && strings.Contains(".mp4.mp3.flac", filepath.Ext(f.Name())) {
            files <- path
        }
        return err
    }

    if err := filepath.Walk(searchPath, visit); err != nil {
        fmt.Println(err)
    }
    close(files)
}

func printHashes(files <-chan string, wg *sync.WaitGroup) {
    defer wg.Done()
    for range files {
        fmt.Println(<-files)
    }
}

请注意,虽然这看起来很快,但使用单个 goroutine 就可以了,并且也可以解除对主 goroutine 的阻塞。但是,如果您尝试在多个 goroutine 中读取 id3 标签的多个文件,您可能不会获得任何优势——它们将在系统调用级别共享相同的文件 i/o 锁。唯一有利的方法是,如果数据处理远远超出文件 i/o 锁定的权重(例如,计算量很大,因为处理速度远快于系统调用锁)。

PS,欢迎来到 Go 社区!