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