我是Go的新手,我正在学习如何使用goroutines。
我有一个下载图片的功能:
func imageDownloader(uri string, filename string) {
fmt.Println("starting download for ", uri)
outFile, err := os.Create(filename)
defer outFile.Close()
if err != nil {
os.Exit(1)
}
client := &http.Client{}
req, err := http.NewRequest("GET", uri, nil)
resp, err := client.Do(req)
defer resp.Body.Close()
if err != nil {
panic(err)
}
header := resp.ContentLength
bar := pb.New(int(header))
rd := bar.NewProxyReader(resp.Body)
// and copy from reader
io.Copy(outFile, rd)
}
当我自己调用另一个函数时,它会完全下载图像并且没有截断的数据。
然而,当我尝试修改它以使其成为goroutine时,图像经常被截断或零长度文件。
func imageDownloader(uri string, filename string, wg *sync.WaitGroup) {
...
io.Copy(outFile, rd)
wg.Done()
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
go imageDownloader(url, file, &wg)
wg.Wait()
}
我是否错误地使用了WaitGroups?什么可能导致这种情况,我该如何解决?
更新
解决了它。我已将wg.add()
函数放在循环之外。 :(
答案 0 :(得分:3)
虽然我不确定究竟是什么导致了您的问题,但这里有两个选项可以让它恢复正常工作。
首先,从同步库中查看example of how to use waitgroups,尝试在函数开头调用defer wg.Done()
,以确保即使goroutine意外结束,等待组也会正确递减。
其次,io.Copy
会返回您未检查的错误。无论如何,这不是很好的练习,但在你的特殊情况下,它会阻止你看到复制例程中是否确实存在错误。检查并妥善处理。它还返回写入的字节数,这对您也有帮助。
答案 1 :(得分:3)
您的示例在使用WaitGroups时没有任何明显错误。只要您使用与您启动的goroutine数量相同的数字调用wg.Add()
,或者每次启动新的goroutine时将其递增1,那就应该是正确的。
但是,对于goroutine中的某些错误条件,您可以调用os.Exit
和panic
,因此如果您运行了多个错误条件,则其中任何一个运行失败都会终止所有错误,无论如何使用WaitGroups。如果它在没有恐慌信息的情况下失败,我会看一下os.Exit(1)
行。
在函数开始时使用defer wg.Done()
也是一种很好的做法,这样即使发生错误,goroutine仍然会减少其计数器。这样,如果其中一个goroutines返回错误,你的主线程就不会挂起。
答案 2 :(得分:0)
当您defer
时,我在您的示例中进行的一项更改是使用Done
。我认为这个defer ws.Done()
应该是你函数中的第一个语句。
我喜欢WaitGroup
的简单。但是,我不喜欢我们需要将引用传递给goroutine,因为这意味着并发逻辑将与您的业务逻辑混合。
所以我想出了这个通用函数来解决这个问题:
// Parallelize parallelizes the function calls
func Parallelize(functions ...func()) {
var waitGroup sync.WaitGroup
waitGroup.Add(len(functions))
defer waitGroup.Wait()
for _, function := range functions {
go func(copy func()) {
defer waitGroup.Done()
copy()
}(function)
}
}
所以你的例子可以通过这种方式解决:
func imageDownloader(uri string, filename string) {
...
io.Copy(outFile, rd)
}
func main() {
functions := []func(){}
list := make([]Object, 5)
for _, object := range list {
function := func(obj Object){
imageDownloader(object.uri, object.filename)
}(object)
functions = append(functions, function)
}
Parallelize(functions...)
fmt.Println("Done")
}
如果您想使用它,可以在https://github.com/shomali11/util
找到它