golang中的死锁错误

时间:2017-01-17 00:13:44

标签: go deadlock

我最近看了一眼就迷上了,它看起来很有趣!完成教程后,我想自己构建一些东西:我想列出我音乐库中的所有歌曲。我想我可以从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

造成僵局的原因是什么?

2 个答案:

答案 0 :(得分: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.
}

答案 1 :(得分:1)

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锁定。唯一有利的方法是,如果数据的处理远远超出文件的锁定(例如,计算中的某些东西,因为处理比系统调用锁快得多)。

PS,欢迎来到Go社区!