去频道无限循环
Go channel infinite loop
我正在尝试使用通道从一组 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")
}
所以我正在循环一片文件并将它们写入我的磁盘,当出现错误时我向 errorChannel
报告以将该错误保存到一个片中。
我使用 sync.WaitGroup
等待所有 goroutines,当它们完成后我想打印 errorList
并检查执行过程中是否有任何错误。
列表总是空的,即使我在writeZipFileToLocal
末尾添加ch <- fmt.Errorf("test")
并且频道总是挂断。
我不确定我在这里遗漏了什么。
1.对于第一点,死循环:
A receive operation on a closed channel can always proceed
immediately, yielding the element type's zero value after any
previously sent values have been received.
所以在这个函数中
func errorChannelWatch(ch chan error, list []error) {
for {
select {
case err := <- ch:
list = append(list, err)
}
}
}
ch 关闭后,这变成了一个无限循环,将 nil
值添加到 list
。
试试这个:
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)
在这里,您将errorChannelWatch
errorList
作为一个值。所以切片 errorList
不会被函数改变。改变的是底层数组,只要append
调用不需要分配新的。
为了补救这种情况,将切片指针传递给 errorChannelWatch
或将其重写为对闭包的调用,捕获
errorList
.
对于第一个建议的解决方案,将 errorChannelWatch
更改为
func errorChannelWatch(ch chan error, list *[]error) {
for err := range ch {
*list = append(*list, err)
}
}
以及对
的调用
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 没有被修改?可以推断,您不知道 goroutine errorChannelWatch
仍然需要处理多少个值。
你的同步对我来说似乎是正确的,就像你做的那样 wg.Done()
在发送到错误通道之后,所有错误值都会
发送,当 fileWaitGroup.Wait()
returns.
但这可能会改变,如果有人后来为错误添加了缓冲
通道或更改代码。
所以我建议至少在评论中解释同步。
我正在尝试使用通道从一组 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")
}
所以我正在循环一片文件并将它们写入我的磁盘,当出现错误时我向 errorChannel
报告以将该错误保存到一个片中。
我使用 sync.WaitGroup
等待所有 goroutines,当它们完成后我想打印 errorList
并检查执行过程中是否有任何错误。
列表总是空的,即使我在writeZipFileToLocal
末尾添加ch <- fmt.Errorf("test")
并且频道总是挂断。
我不确定我在这里遗漏了什么。
1.对于第一点,死循环:
A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.
所以在这个函数中
func errorChannelWatch(ch chan error, list []error) {
for {
select {
case err := <- ch:
list = append(list, err)
}
}
}
ch 关闭后,这变成了一个无限循环,将 nil
值添加到 list
。
试试这个:
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)
在这里,您将errorChannelWatch
errorList
作为一个值。所以切片 errorList
不会被函数改变。改变的是底层数组,只要append
调用不需要分配新的。
为了补救这种情况,将切片指针传递给 errorChannelWatch
或将其重写为对闭包的调用,捕获
errorList
.
对于第一个建议的解决方案,将 errorChannelWatch
更改为
func errorChannelWatch(ch chan error, list *[]error) {
for err := range ch {
*list = append(*list, err)
}
}
以及对
的调用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 没有被修改?可以推断,您不知道 goroutine errorChannelWatch
仍然需要处理多少个值。
你的同步对我来说似乎是正确的,就像你做的那样 wg.Done()
在发送到错误通道之后,所有错误值都会
发送,当 fileWaitGroup.Wait()
returns.
但这可能会改变,如果有人后来为错误添加了缓冲 通道或更改代码。
所以我建议至少在评论中解释同步。