2017-08-20 6 views
0

チャンネルを使用してゴルーチンのグループからエラーをキャッチしようとしていますが、チャンネルが無限ループに入り、CPUを使い始めます。チャンネルの無限ループに移動

func UnzipFile(f *bytes.Buffer, location string) error { 
    zipReader, err := zip.NewReader(bytes.NewReader(f.Bytes()), int64(f.Len())) 

    if err != nil { 
     return err 
    } 

    if err := os.MkdirAll(location, os.ModePerm); err != nil { 
     return err 
    } 

    errorChannel := make(chan error) 
    errorList := []error{} 

    go errorChannelWatch(errorChannel, errorList) 

    fileWaitGroup := &sync.WaitGroup{} 

    for _, file := range zipReader.File { 
     fileWaitGroup.Add(1) 
     go writeZipFileToLocal(file, location, errorChannel, fileWaitGroup) 
    } 

    fileWaitGroup.Wait() 

    close(errorChannel) 

    log.Println(errorList) 

    return nil 
} 

func errorChannelWatch(ch chan error, list []error) { 
    for { 
     select { 
     case err := <- ch: 

      list = append(list, err) 
     } 
    } 
} 

func writeZipFileToLocal(file *zip.File, location string, ch chan error, wg *sync.WaitGroup) { 
    defer wg.Done() 

    zipFilehandle, err := file.Open() 

    if err != nil { 
     ch <- err 
     return 
    } 

    defer zipFilehandle.Close() 

    if file.FileInfo().IsDir() { 
     if err := os.MkdirAll(filepath.Join(location, file.Name), os.ModePerm); err != nil { 
      ch <- err 
     } 
     return 
    } 

    localFileHandle, err := os.OpenFile(filepath.Join(location, file.Name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) 

    if err != nil { 
     ch <- err 
     return 
    } 

    defer localFileHandle.Close() 

    if _, err := io.Copy(localFileHandle, zipFilehandle); err != nil { 
     ch <- err 
     return 
    } 

    ch <- fmt.Errorf("Test error") 
} 

私はスライスにそのエラーを保存するために戻ってerrorChannelに報告し、エラーがあった場合だから私は、ファイルのスライスをループして、私のディスクに書いています。

私はすべてのゴルーチンを待つのにsync.WaitGroupを使用し、完了したらerrorListを印刷し、実行中にエラーがないかどうかをチェックします。

writeZipFileToLocalの末尾にch <- fmt.Errorf("test")を追加しても、リストは常に空です。チャンネルは常にハングアップします。

私はここで何が欠けているのか分かりません。第一の点について

答えて

2

1、無限ループ:golang language specから引用

Aが閉じたチャネルで受信動作は常に要素型のゼロ値を得、すぐ を進めることができます以前に送信された値のいずれかが受信された後。

したがって、この関数で

func errorChannelWatch(ch chan error, list []error) { 
    for { 
     select { 
     case err := <- ch: 

      list = append(list, err) 
     } 
    } 
} 

CHを閉じ取得した後、これはlistnil値を加算無限ループになります。

代わりにこれを試してください:あなたはエラーリストには何も表示されない理由は第二の点については、

func errorChannelWatch(ch chan error, list []error) { 
    for err := range ch { 
      list = append(list, err) 
    } 
} 

2:

を問題は、この呼び出しです:

errorChannel := make(chan error) 
errorList := []error{} 

go errorChannelWatch(errorChannel, errorList) 

errorChannelWatcherrorListを値として返します。したがって、スライスerrorListは機能によって変更されません。変更されたものは、append呼び出しが新しいものを割り当てる必要がない限り、基本となる配列です。

状況を解決するには、errorChannelWatchにスライスポインタを渡すか、またはクロージャーの呼び出しとして再書き込みして、 errorListをキャプチャします。最初の提案された解決策について

func errorChannelWatch(ch chan error, list *[]error) { 
    for err := range ch { 
      *list = append(*list, err) 
    } 
}  

errorChannelWatchを変更し、第二の提案されたソリューションについて

errorChannel := make(chan error) 
errorList := []error{} 

go errorChannelWatch(errorChannel, &errorList) 

へのコールだけ

errorChannel := make(chan error) 
    errorList := []error{} 

    go func() { 
     for err := range errorChannel { 
      errorList = append(errorList, err) 
     } 
    }() 

への呼び出しを変更します3.マイナー発言:

一つはここに同期の問題があることを、考えることができます:

fileWaitGroup.Wait() 

close(errorChannel) 

log.Println(errorList) 

どのように呼び出しが閉じるようにした後errorListは、変更されていないことを、確認することができますか?ゴルーチンerrorChannelWatchがまだ処理しなければならない値がいくつあるのか、あなたは知ることができません。あなたがエラーチャネルに送信した後wg.Done() を行うとするときfileWaitGroup.Wait()戻りますので、すべてのエラー値が 、送信されますよう

あなたの同期は、私には正しいようです。

しかし、誰かが後でエラー チャネルにバッファリングを追加したり、コードを変更したりすると、変更される可能性があります。

私は少なくともコメントの同期について説明することをお勧めします。