Go频道无限循环

Rag*_*aer 4 channel go

我试图使用通道捕获一组 goroutine 中的错误,但通道进入无限循环,开始消耗 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")
}
Run Code Online (Sandbox Code Playgroud)

因此,我循环一个文件切片并将它们写入磁盘,当出现错误时,我会向 报告以将该errorChannel错误保存到一个切片中。

我使用 async.WaitGroup来等待所有 goroutine,当它们完成时,我想打印errorList并检查执行过程中是否有任何错误。

即使我ch <- fmt.Errorf("test")在末尾添加writeZipFileToLocal并且频道总是挂断,列表始终为空。

我不确定我在这里缺少什么。

typ*_*ris 8

1.对于第一点,无限循环:

引用golang 语言规范

关闭通道上的接收操作始终可以立即进行,在收到任何先前发送的值后生成元素类型的零值。

所以在这个函数中

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

            list = append(list, err)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

ch 关闭后,这会变成一个无限循环,将nil值添加到list

试试这个:

func errorChannelWatch(ch chan error, list []error) {
    for err := range ch {
            list = append(list, err)
    }
}
Run Code Online (Sandbox Code Playgroud)

2.对于第二点,为什么你在错误列表中看不到任何内容:

问题是这个调用:

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

go errorChannelWatch(errorChannel, errorList)
Run Code Online (Sandbox Code Playgroud)

在这里你将errorChannelWatch作为errorList一个值。所以切片errorList不会被函数改变。改变的是底层数组,只要append调用不需要分配新数组即可。

要解决这种情况,请将切片指针传递给errorChannelWatch或将其重写为对闭包的调用,捕获 errorList.

对于第一个建议的解决方案,更改errorChannelWatch

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

并致电

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

go errorChannelWatch(errorChannel, &errorList)
Run Code Online (Sandbox Code Playgroud)

对于第二个建议的解决方案,只需将调用更改为

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

   go func() {
      for err := range errorChannel {
          errorList = append(errorList, err)
      }
   } () 
Run Code Online (Sandbox Code Playgroud)

3. 一点小意见:

人们可能会认为这里存在同步问题:

fileWaitGroup.Wait()

close(errorChannel)

log.Println(errorList)
Run Code Online (Sandbox Code Playgroud)

在调用关闭之后,您如何确定 errorList 没有被修改?人们可能会推断,你无法知道 goroutineerrorChannelWatch仍然需要处理多少值。

您的同步对我来说似乎是正确的,因为您在wg.Done() 发送到错误通道后执行此操作,因此fileWaitGroup.Wait()返回时将发送所有错误值。

但如果后来有人向错误通道添加缓冲或更改代码,情况可能会改变。

所以我建议至少在评论中解释同步。