2017-01-17 2 views
3

최근에 보니 흥미로 웠습니다. 튜토리얼을 마친 후에는 혼자서 무언가를 만들고 싶었습니다. 음악 라이브러리의 모든 노래를 나열하고 싶습니다. 나는 여기서 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

항상 WaitGroup.Done()을 연기하십시오. 완료라고하지 않는 첫 번째 goroutine의 반환 경로가 있습니다. – JimB

+1

@JimB 거의 저도 있어요. 그러나 리턴 경로가 아니라는 것을 알아 두십시오. 인라인 함수는 선언되어 다른 함수로 전달됩니다.이 함수는 하나의 실행 경로 만 가지고 있습니다. 하지만 네, 전형적인 관행은'defer wg.Done() '을 호출해야합니다. – eduncan911

+0

@ eduncan911 : 감사합니다. 조기 반환을 스캔하고 충분히 자세히 읽지 않았습니다. – JimB

답변

1

searchFiles 내에서 close(files)하려는. 이 규칙을 송신자 폐쇄 (수신자가 절대로 닫히지 않음)라고합니다. 또한 완료되지 않은 경우 wg.Done()으로 전화를 제거하십시오. 채널에 항목이 남아있을 수 있습니다.

close(files)for range files이 모든 것을 수행하는 주요 기능을 신호하기 위해 wg.Done()를 호출 루프를 닫고 종료 신호를 보냅니다. 이 빠른 것처럼 보일 수 있지만, 하나의 goroutine을 사용하는 것은 괜찮 너무 주요 goroutine 차단을 해제하는 것이

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에있는 id3 태그에 대해 여러 파일을 읽으려고하면 모든 이득을 얻을 수 없습니다 - 모두 syscall 수준에서 동일한 파일 I/O 잠금을 공유합니다. 유익한 유일한 방법은 멀리있는 데이터를 처리 할 때 파일 I/O 잠금 (예 : 처리가 시스템 콜 잠금보다 훨씬 빠르기 때문에 큰 값)을하는 경우입니다.

PS, Go 커뮤니티에 오신 것을 환영합니다!

+0

syscall 레벨 파일 잠금에 대한 자세한 대답과 힌트를 보내 주셔서 감사합니다. 저의 초기 아이디어는 500GB 음악 라이브러리의 디렉토리 트리를 걷는 것이 시간이 걸릴 수 있다는 것입니다. 따라서 가능한 한 빨리 태그를 읽는 것을 시작하십시오 (훨씬 느립니다). – LuMa

+1

@LuMa 실제로 MP3 태그를 읽는 것은 꽤 빠릅니다. 그것은 MP3 내에서 MP3 태그의 바이트 코드 오프셋을 잘 알고 있습니다. 따라서, 그것은 빠른'open file -> read byte offset locations -> close file' 연산입니다.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. 
}