2017-01-17 3 views
3

Недавно я посмотрел на ходу и зацепился, это выглядит так интересно! После завершения учебного пособия мне захотелось что-то создать самостоятельно: я хочу перечислить все свои песни из своей музыкальной библиотеки. Я думаю, что я могу получить прибыль от параллелизма с вами. В то время как на рутине идет по дереву каталогов, он подталкивает музыкальные файлы (путь к этим файлам) в канал, который затем подбирается другой подпрограммой, которая читает теги ID3, поэтому мне не нужно ждать, пока не будет найден все файлы ,Ошибка тупика в golang

Это мой простой и наивный подход:

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

Всегда откладывайте WaitGroup.Done(). У вас есть обратный путь в вашем первом goroutine, который не вызывает Done. – JimB

+1

@ JimB, который меня тоже доставил. Но обратите внимание, что это не путь возврата - это встроенный func, объявленный и переданный в другую функцию, которая имеет только один путь выполнения. Но да, типичные практики требуют вызова 'defer wg.Done()' – eduncan911

+0

@ eduncan911: oops, thanks. Просто сканировался на ранние возвращения и не читал достаточно внимательно. – JimB

ответ

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 тоже. Но вы не можете получить какое-либо преимущество, если попытаетесь прочитать несколько файлов для тегов id3 в нескольких goroutines - все они будут использовать один и тот же файл i/o lock на уровне syscall. Единственный способ, который мог бы быть полезен, заключался бы в том, что обработка данных далеко не влияет на блокировку ввода/вывода файла (например, что-то большое в вычислении, потому что обработка намного быстрее, чем блокировки syscall).

PS, добро пожаловать в сообщество Go!

+0

Спасибо за подробный ответ и подсказку о блокировке файлов уровня syscall. Моя первоначальная идея заключалась в том, что ходьба по дереву каталогов в библиотеке объемом 500 ГБ может занять некоторое время. Так почему бы не начать читать теги (которые намного медленнее) как можно раньше. – LuMa

+1

@ LuMa На самом деле, чтение MP3-тегов довольно быстро. Он хорошо знает смещения байт-кода MP3-тега в MP3. И, следовательно, это быстрый 'открытый файл -> чтение байтов смещения местоположения -> закрыть файл'.syscalls по-прежнему будет основным замком, который будет убийцей производительности. Если ... они на разных дисках. :) – eduncan911

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. 
} 
Смежные вопросы